From d3930ef22637c2d24dc3625dbb53e129ab37afeb Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 7 Feb 2021 00:18:37 +0200 Subject: [PATCH 01/69] Basic move tool --- project.godot | 10 ++++++++ src/Autoload/Global.gd | 12 +++++++-- src/Autoload/Tools.gd | 1 + src/Tools/Move.gd | 55 +++++++++++++++++++++++++++++++++++++++++ src/Tools/Move.tscn | 23 +++++++++++++++++ src/UI/Canvas/Canvas.gd | 8 ++++-- src/UI/ToolButtons.gd | 1 + src/UI/UI.tscn | 47 ++++++++++++++++++++++++----------- 8 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 src/Tools/Move.gd create mode 100644 src/Tools/Move.tscn diff --git a/project.godot b/project.godot index bd4662a82a45..dbb7251aef41 100644 --- a/project.godot +++ b/project.godot @@ -488,6 +488,16 @@ right_ellipsetool_tool={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":67,"unicode":0,"echo":false,"script":null) ] } +left_move_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) + ] +} +right_move_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 125a3939e0a5..a3d231920868 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -477,9 +477,10 @@ func change_button_texturerect(texture_button : TextureRect, new_file_name : Str func update_hint_tooltips() -> void: - var root = get_tree().get_root() + var root = control + var tool_buttons = root.find_node("ToolButtons") - var rect_select : BaseButton = find_node_by_name(root, "RectSelect") + var rect_select : BaseButton = tool_buttons.find_node("RectSelect") rect_select.hint_tooltip = tr("""Rectangular Selection %s for left mouse button @@ -487,6 +488,13 @@ func update_hint_tooltips() -> void: Press %s to move the content""") % [InputMap.get_action_list("left_rectangle_select_tool")[0].as_text(), InputMap.get_action_list("right_rectangle_select_tool")[0].as_text(), "Shift"] + var move_select : BaseButton = tool_buttons.find_node("Move") + move_select.hint_tooltip = tr("""Move + +%s for left mouse button +%s for right mouse button""") % [InputMap.get_action_list("left_move_tool")[0].as_text(), InputMap.get_action_list("right_move_tool")[0].as_text()] + + var zoom_tool : BaseButton = find_node_by_name(root, "Zoom") zoom_tool.hint_tooltip = tr("""Zoom diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd index d503550dbbcd..4bd43109a515 100644 --- a/src/Autoload/Tools.gd +++ b/src/Autoload/Tools.gd @@ -40,6 +40,7 @@ signal color_changed(color, button) var _tools = { "RectSelect" : "res://src/Tools/RectSelect.tscn", + "Move" : "res://src/Tools/Move.tscn", "Zoom" : "res://src/Tools/Zoom.tscn", "Pan" : "res://src/Tools/Pan.tscn", "ColorPicker" : "res://src/Tools/ColorPicker.tscn", diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd new file mode 100644 index 000000000000..105b30f13c1d --- /dev/null +++ b/src/Tools/Move.gd @@ -0,0 +1,55 @@ +extends BaseTool + + +var starting_pos : Vector2 +var offset : Vector2 + + +func draw_start(position : Vector2) -> void: + starting_pos = position + offset = position + if Global.current_project.selected_pixels: + Global.selection_rectangle.move_start(true) + + +func draw_move(position : Vector2) -> void: + if Global.current_project.selected_pixels: + Global.selection_rectangle.move_rect(position - offset) + else: + Global.canvas.move_preview_location = position - starting_pos + offset = position + + +func draw_end(position : Vector2) -> void: + if starting_pos != Vector2.INF: + var pixel_diff : Vector2 = position - starting_pos + if pixel_diff != Vector2.ZERO: + var project : Project = Global.current_project + var image : Image = _get_draw_image() +# var pixels := [] +# if project.selected_pixels: +# pixels = project.selected_pixels.duplicate() +# else: +# for x in Global.current_project.size.x: +# for y in Global.current_project.size.y: +# var pos := Vector2(x, y) +# pixels.append([pos, image.get_pixelv(pos)]) + +# print(pixels[3]) + if project.selected_pixels: + Global.selection_rectangle.move_end() + else: + Global.canvas.move_preview_location = Vector2.ZERO + var image_copy := Image.new() + image_copy.copy_from(image) + Global.canvas.handle_undo("Draw") + image.fill(Color(0, 0, 0, 0)) + # image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) + image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) + # for pixel in pixels: + ## image.set_pixelv(pixel[0] + pixel_diff, Color.red) + # image.set_pixelv(pixel[0] + pixel_diff, pixel[1]) + Global.canvas.handle_redo("Draw") + + print(pixel_diff) + starting_pos = Vector2.INF diff --git a/src/Tools/Move.tscn b/src/Tools/Move.tscn new file mode 100644 index 000000000000..82037f1ba8ba --- /dev/null +++ b/src/Tools/Move.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/BaseTool.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/Move.gd" type="Script" id=2] + + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="PixelPerfect" parent="." index="1"] +visible = false +margin_top = 126.0 +margin_bottom = 150.0 + +[node name="EmptySpacer" parent="." index="2"] +visible = false +margin_top = 126.0 +margin_bottom = 138.0 + +[node name="Mirror" parent="." index="3"] +visible = false +margin_top = 126.0 +margin_bottom = 143.0 diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index 92e4d3579acc..b90140d52eec 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -7,6 +7,7 @@ var current_pixel := Vector2.ZERO var can_undo := true var cursor_image_has_changed := false var sprite_changed_this_frame := false # for optimization purposes +var move_preview_location := Vector2.ZERO onready var currently_visible_frame : Viewport = $CurrentlyVisibleFrame onready var current_frame_drawer = $CurrentlyVisibleFrame/CurrentFrameDrawer @@ -30,7 +31,7 @@ func _draw() -> void: Global.small_preview_viewport.get_child(0).get_node("CanvasPreview").update() var current_cels : Array = Global.current_project.frames[Global.current_project.current_frame].cels - + var current_layer : int = Global.current_project.current_layer var _position := position var _scale := scale if Global.mirror_view: @@ -41,7 +42,10 @@ func _draw() -> void: for i in range(Global.current_project.layers.size()): var modulate_color := Color(1, 1, 1, current_cels[i].opacity) if Global.current_project.layers[i].visible: # if it's visible - draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) + if i == current_layer: + draw_texture(current_cels[i].image_texture, move_preview_location, modulate_color) + else: + draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) if Global.onion_skinning: onion_skinning() diff --git a/src/UI/ToolButtons.gd b/src/UI/ToolButtons.gd index fc7643873337..1785142963fe 100644 --- a/src/UI/ToolButtons.gd +++ b/src/UI/ToolButtons.gd @@ -4,6 +4,7 @@ extends VBoxContainer # Node, shortcut onready var tools := [ [$RectSelect, "rectangle_select"], + [$Move, "move"], [$Zoom, "zoom"], [$Pan, "pan"], [$ColorPicker, "colorpicker"], diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 1084e4a0553f..827c54352e7b 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=29 format=2] +[gd_scene load_steps=30 format=2] [ext_resource path="res://src/UI/ToolButtons.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/CanvasPreview.tscn" type="PackedScene" id=2] @@ -25,6 +25,7 @@ [ext_resource path="res://src/UI/ViewportContainer.gd" type="Script" id=23] [ext_resource path="res://assets/graphics/dark_themes/tools/rectangletool.png" type="Texture" id=24] [ext_resource path="res://assets/graphics/dark_themes/tools/ellipsetool.png" type="Texture" id=25] +[ext_resource path="res://assets/graphics/dark_themes/tools/move.png" type="Texture" id=26] [sub_resource type="ShaderMaterial" id=1] shader = ExtResource( 9 ) @@ -113,7 +114,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="Zoom" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +[node name="Move" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] margin_top = 36.0 @@ -123,6 +124,24 @@ rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 +[node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Move"] +margin_right = 32.0 +margin_bottom = 32.0 +texture = ExtResource( 24 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Zoom" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +"UIButtons", +]] +margin_top = 72.0 +margin_right = 32.0 +margin_bottom = 104.0 +rect_min_size = Vector2( 32, 32 ) +mouse_default_cursor_shape = 2 +button_mask = 3 + [node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Zoom"] margin_right = 32.0 margin_bottom = 32.0 @@ -134,9 +153,9 @@ __meta__ = { [node name="Pan" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 72.0 +margin_top = 108.0 margin_right = 32.0 -margin_bottom = 104.0 +margin_bottom = 140.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -152,9 +171,9 @@ __meta__ = { [node name="ColorPicker" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 108.0 +margin_top = 144.0 margin_right = 32.0 -margin_bottom = 140.0 +margin_bottom = 176.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -170,9 +189,9 @@ __meta__ = { [node name="Pencil" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 144.0 +margin_top = 180.0 margin_right = 32.0 -margin_bottom = 176.0 +margin_bottom = 212.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -188,9 +207,9 @@ __meta__ = { [node name="Eraser" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 180.0 +margin_top = 216.0 margin_right = 32.0 -margin_bottom = 212.0 +margin_bottom = 248.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -206,9 +225,9 @@ __meta__ = { [node name="Bucket" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 216.0 +margin_top = 252.0 margin_right = 32.0 -margin_bottom = 248.0 +margin_bottom = 284.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -224,9 +243,9 @@ __meta__ = { [node name="LightenDarken" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 252.0 +margin_top = 288.0 margin_right = 32.0 -margin_bottom = 284.0 +margin_bottom = 320.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 From a8c5765d7fab71edc7b1f70ff3b73a21637c3832 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Mon, 8 Feb 2021 01:56:49 +0200 Subject: [PATCH 02/69] Added marching ants effect on the selection borders --- src/SelectionRectangle.gd | 93 +++++++++++++++++++++++++++++++++++++++ src/UI/UI.tscn | 2 +- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/SelectionRectangle.gd b/src/SelectionRectangle.gd index 479c4536d415..8c768813c7b5 100644 --- a/src/SelectionRectangle.gd +++ b/src/SelectionRectangle.gd @@ -1,6 +1,8 @@ extends Polygon2D +var line_offset := Vector2.ZERO setget _offset_changed +var tween : Tween var _selected_rect := Rect2(0, 0, 0, 0) var _clipped_rect := Rect2(0, 0, 0, 0) var _move_image := Image.new() @@ -12,15 +14,106 @@ var _undo_data := {} func _ready() -> void: + tween = Tween.new() + tween.connect("tween_completed", self, "_offset_tween_completed") + add_child(tween) + tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) + tween.start() _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) _clear_image.fill(Color(0, 0, 0, 0)) +# set_rect(Rect2(16, 20, 4, 4)) + + +func _offset_tween_completed(_object, _key) -> void: + self.line_offset = Vector2.ZERO + tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) + tween.start() + + +func _offset_changed(value : Vector2) -> void: + line_offset = value + update() func _draw() -> void: + var points : PoolVector2Array = polygon + for i in range(1, points.size() + 1): + var point0 = points[i - 1] + var point1 + if i >= points.size(): + point1 = points[0] + else: + point1 = points[i] + var start_x = min(point0.x, point1.x) + var start_y = min(point0.y, point1.y) + var end_x = max(point0.x, point1.x) + var end_y = max(point0.y, point1.y) + + var start := Vector2(start_x, start_y) + var end := Vector2(end_x, end_y) + draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) + if _move_pixel: draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) +# Taken and modified from https://github.com/juddrgledhill/godot-dashed-line +func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Color, width := 1.0, dash_length := 1.0, cap_end := false, antialiased := false) -> void: + var length = (to - from).length() + var normal = (to - from).normalized() + var dash_step = normal * dash_length + + var horizontal : bool = from.y == to.y + var _offset : Vector2 + if horizontal: + _offset = Vector2(line_offset.x, 0) + else: + _offset = Vector2(0, line_offset.y) + + if length < dash_length: # not long enough to dash + draw_line(from, to, color, width, antialiased) + return + + else: + var draw_flag = true + var segment_start = from + var steps = length/dash_length + for _start_length in range(0, steps + 1): + var segment_end = segment_start + dash_step + + var start = segment_start + _offset + start.x = min(start.x, to.x) + start.y = min(start.y, to.y) + + var end = segment_end + _offset + end.x = min(end.x, to.x) + end.y = min(end.y, to.y) + if draw_flag: + draw_line(start, end, color, width, antialiased) + else: + draw_line(start, end, color2, width, antialiased) + if _offset.length() < 1: + draw_line(from, from + _offset, color2, width, antialiased) + else: + var from_offseted : Vector2 = from + _offset + var halfway_point : Vector2 = from_offseted + if horizontal: + halfway_point += Vector2.LEFT + else: + halfway_point += Vector2.UP + + from_offseted.x = min(from_offseted.x, to.x) + from_offseted.y = min(from_offseted.y, to.y) + draw_line(halfway_point, from_offseted, color2, width, antialiased) + draw_line(from, halfway_point, color, width, antialiased) + + segment_start = segment_end + draw_flag = !draw_flag + + if cap_end: + draw_line(segment_start, to, color, width, antialiased) + + func has_point(position : Vector2) -> bool: return _selected_rect.has_point(position) diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 827c54352e7b..7ba18f57f219 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -389,7 +389,7 @@ script = ExtResource( 7 ) [node name="SelectionRectangle" type="Polygon2D" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer/ViewportandVerticalRuler/ViewportContainer/Viewport"] visible = false z_index = 1 -color = Color( 0.0823529, 0.694118, 0.623529, 0.592157 ) +color = Color( 1, 1, 1, 0 ) invert_enable = true invert_border = 0.5 polygon = PoolVector2Array( 0, 0, 0, 0, 0, 0, 0, 0 ) From 9d1597894e9a5cc84236fe61c471932acdc05c61 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 13 Feb 2021 16:56:21 +0200 Subject: [PATCH 03/69] Rename SelectionRectangle to SelectionShape, make it have non-rectangular shape and multiple SelectionShapes can exist - Create multiple selection rectangles - Merge them together if they intersect - Move the selections (without contents as of right now) - Gizmos are being drawn but they are not functional yet Code is very ugly. --- project.godot | 12 ++ src/Autoload/Global.gd | 2 - src/Classes/Project.gd | 26 ++-- src/Classes/Selection.gd | 11 ++ src/Tools/Move.gd | 11 +- src/Tools/RectSelect.gd | 86 +++++++++---- .../SelectionShape.gd} | 115 ++++++++++++++---- src/Tools/SelectionShape.tscn | 12 ++ src/UI/TopMenuContainer.gd | 17 ++- src/UI/UI.tscn | 10 -- 10 files changed, 229 insertions(+), 73 deletions(-) create mode 100644 src/Classes/Selection.gd rename src/{SelectionRectangle.gd => Tools/SelectionShape.gd} (71%) create mode 100644 src/Tools/SelectionShape.tscn diff --git a/project.godot b/project.godot index dbb7251aef41..69403e163fac 100644 --- a/project.godot +++ b/project.godot @@ -99,6 +99,16 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/Project.gd" }, { +"base": "Reference", +"class": "Selection", +"language": "GDScript", +"path": "res://src/Classes/Selection.gd" +}, { +"base": "Polygon2D", +"class": "SelectionShape", +"language": "GDScript", +"path": "res://src/Tools/SelectionShape.gd" +}, { "base": "Guide", "class": "SymmetryGuide", "language": "GDScript", @@ -123,6 +133,8 @@ _global_script_class_icons={ "PaletteSwatch": "", "Patterns": "", "Project": "", +"Selection": "", +"SelectionShape": "", "SymmetryGuide": "" } diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index a3d231920868..01a2d7ec390d 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -114,7 +114,6 @@ var small_preview_viewport : ViewportContainer var camera : Camera2D var camera2 : Camera2D var camera_preview : Camera2D -var selection_rectangle : Polygon2D var horizontal_ruler : BaseButton var vertical_ruler : BaseButton var transparent_checker : ColorRect @@ -218,7 +217,6 @@ func _ready() -> void: camera = find_node_by_name(main_viewport, "Camera2D") camera2 = find_node_by_name(root, "Camera2D2") camera_preview = find_node_by_name(root, "CameraPreview") - selection_rectangle = find_node_by_name(root, "SelectionRectangle") horizontal_ruler = find_node_by_name(root, "HorizontalRuler") vertical_ruler = find_node_by_name(root, "VerticalRuler") transparent_checker = find_node_by_name(root, "TransparentChecker") diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index f62219cb00f7..6fe83bb83092 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -1,7 +1,6 @@ class_name Project extends Reference # A class for project properties. - var name := "" setget name_changed var size : Vector2 setget size_changed var undo_redo : UndoRedo @@ -24,7 +23,7 @@ var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide var selected_pixels := [] -var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect +var selections := [] setget _set_selections # Array of SelectionShape(s) # For every camera (currently there are 3) var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2 @@ -85,9 +84,9 @@ func clear_selection() -> void: selected_pixels.clear() -func _set_selected_rect(value : Rect2) -> void: - selected_rect = value - Global.selection_rectangle.set_rect(value) +func _set_selections(value : Array) -> void: + selections = value +# Global.selection_rectangl.set_rect(value) func change_project() -> void: @@ -147,7 +146,7 @@ func change_project() -> void: self.animation_tags = animation_tags # Change the selection rectangle - Global.selection_rectangle.set_rect(selected_rect) +# Global.selection_rectangl.set_rect(selected_rect) # Change the guides for guide in Global.canvas.get_children(): @@ -363,7 +362,7 @@ func name_changed(value : String) -> void: func size_changed(value : Vector2) -> void: size = value update_tile_mode_rects() - Global.selection_rectangle.set_rect(Global.selection_rectangle.get_rect()) +# Global.selection_rectangl.set_rect(Global.selection_rectangl.get_rect()) func frames_changed(value : Array) -> void: @@ -582,3 +581,16 @@ func update_tile_mode_rects() -> void: func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 + + +func get_selection_image() -> Image: + var image := Image.new() + var cel_image : Image = frames[current_frame].cels[current_layer].image + image.copy_from(cel_image) + image.lock() + image.fill(Color(0, 0, 0, 0)) + for pixel in selected_pixels: + var color : Color = cel_image.get_pixelv(pixel) + image.set_pixelv(pixel, color) + image.unlock() + return image diff --git a/src/Classes/Selection.gd b/src/Classes/Selection.gd new file mode 100644 index 000000000000..1652ba483585 --- /dev/null +++ b/src/Classes/Selection.gd @@ -0,0 +1,11 @@ +class_name Selection extends Reference + + +var selected_area := [] # Selected pixels for each selection +var borders : PoolVector2Array +var node : SelectionShape + + +func _init(_node : SelectionShape) -> void: + node = _node + Global.canvas.add_child(node) diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index 105b30f13c1d..f0ef6413657d 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -9,12 +9,16 @@ func draw_start(position : Vector2) -> void: starting_pos = position offset = position if Global.current_project.selected_pixels: - Global.selection_rectangle.move_start(true) + pass +# Global.selection_rectangl.move_start(true) func draw_move(position : Vector2) -> void: if Global.current_project.selected_pixels: - Global.selection_rectangle.move_rect(position - offset) + for selection in Global.current_project.selections: + selection.move_polygon(position - offset) + offset = position +# Global.selection_rectangl.move_rect(position - offset) else: Global.canvas.move_preview_location = position - starting_pos offset = position @@ -37,7 +41,8 @@ func draw_end(position : Vector2) -> void: # print(pixels[3]) if project.selected_pixels: - Global.selection_rectangle.move_end() + for selection in Global.current_project.selections: + selection.select_rect() else: Global.canvas.move_preview_location = Vector2.ZERO var image_copy := Image.new() diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index e4f10c8a5a8b..e22dad756dcc 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -1,6 +1,8 @@ extends BaseTool +var current_selection_id := -1 +var start_position := Vector2.INF var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO var _drag := false @@ -8,49 +10,85 @@ var _move := false func draw_start(position : Vector2) -> void: - if Global.selection_rectangle.has_point(position): + var i := 0 + for selection in Global.current_project.selections: + if selection.has_point(position): + current_selection_id = i + i += 1 + + if current_selection_id == -1: + current_selection_id = Global.current_project.selections.size() + var selection_shape := preload("res://src/Tools/SelectionShape.tscn").instance() + Global.current_project.selections.append(selection_shape) + Global.canvas.add_child(selection_shape) + _start = Rect2(position, Vector2.ZERO) + selection_shape.set_rect(_start) + else: + var selection : SelectionShape = Global.current_project.selections[current_selection_id] _move = true _offset = position - Global.selection_rectangle.move_start(Tools.shift) - _set_cursor_text(Global.selection_rectangle.get_rect()) - else: - _drag = true - _start = Rect2(position, Vector2.ZERO) - Global.selection_rectangle.set_rect(_start) + start_position = position + _set_cursor_text(selection.get_rect()) +# if Global.selection_rectangle.has_point(position): +# _move = true +# _offset = position +# Global.selection_rectangle.move_start(Tools.shift) +# _set_cursor_text(Global.selection_rectangle.get_rect()) +# else: +# _drag = true +# _start = Rect2(position, Vector2.ZERO) +# Global.selection_rectangle.set_rect(_start) func draw_move(position : Vector2) -> void: + var selection : SelectionShape = Global.current_project.selections[current_selection_id] + if _move: - Global.selection_rectangle.move_rect(position - _offset) + for _selection in Global.current_project.selections: + _selection.move_polygon(position - _offset) _offset = position - _set_cursor_text(Global.selection_rectangle.get_rect()) + _set_cursor_text(selection.get_rect()) else: var rect := _start.expand(position).abs() rect = rect.grow_individual(0, 0, 1, 1) - Global.selection_rectangle.set_rect(rect) + selection.set_rect(rect) _set_cursor_text(rect) +# if _move: +# Global.selection_rectangle.move_rect(position - _offset) +# _offset = position +# _set_cursor_text(Global.selection_rectangle.get_rect()) +# else: +# var rect := _start.expand(position).abs() +# rect = rect.grow_individual(0, 0, 1, 1) +# Global.selection_rectangle.set_rect(rect) +# _set_cursor_text(rect) -func draw_end(_position : Vector2) -> void: +func draw_end(position : Vector2) -> void: if _move: - Global.selection_rectangle.move_end() + for _selection in Global.current_project.selections: + _selection.move_polygon_end(position, start_position) else: - Global.selection_rectangle.select_rect() - _drag = false + var selection : SelectionShape = Global.current_project.selections[current_selection_id] + selection.select_rect() +# _drag = false _move = false cursor_text = "" + start_position = Vector2.INF + current_selection_id = -1 -func cursor_move(position : Vector2) -> void: - if _drag: - _cursor = Vector2.INF - elif Global.selection_rectangle.has_point(position): - _cursor = Vector2.INF - Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_MOVE - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - else: - _cursor = position - Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS +func cursor_move(_position : Vector2) -> void: + pass +# if _drag: +# _cursor = Vector2.INF +# elif Global.selection_rectangle.has_point(position): +# _cursor = Vector2.INF +# Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_MOVE +# Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) +# else: +# _cursor = position +# Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS func _set_cursor_text(rect : Rect2) -> void: diff --git a/src/SelectionRectangle.gd b/src/Tools/SelectionShape.gd similarity index 71% rename from src/SelectionRectangle.gd rename to src/Tools/SelectionShape.gd index 8c768813c7b5..7fa4c2c135ce 100644 --- a/src/SelectionRectangle.gd +++ b/src/Tools/SelectionShape.gd @@ -1,8 +1,10 @@ -extends Polygon2D +class_name SelectionShape extends Polygon2D var line_offset := Vector2.ZERO setget _offset_changed var tween : Tween +var local_selected_pixels := [] setget _local_selected_pixels_changed # Array of Vector2s +var clear_selection_on_tree_exit := true var _selected_rect := Rect2(0, 0, 0, 0) var _clipped_rect := Rect2(0, 0, 0, 0) var _move_image := Image.new() @@ -53,6 +55,20 @@ func _draw() -> void: var end := Vector2(end_x, end_y) draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) + if !local_selected_pixels: + return + var rect_pos := _selected_rect.position + var rect_end := _selected_rect.end + draw_circle(rect_pos, 1, Color.gray) + draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) + draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) + draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + draw_circle(rect_end, 1, Color.gray) + draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) + draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) + draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) + draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + if _move_pixel: draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) @@ -114,8 +130,22 @@ func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Colo draw_line(segment_start, to, color, width, antialiased) +func _local_selected_pixels_changed(value : Array) -> void: + for pixel in local_selected_pixels: + if pixel in Global.current_project.selected_pixels: + Global.current_project.selected_pixels.erase(pixel) + + local_selected_pixels = value + + for pixel in local_selected_pixels: + if pixel in Global.current_project.selected_pixels: + continue + else: + Global.current_project.selected_pixels.append(pixel) + + func has_point(position : Vector2) -> bool: - return _selected_rect.has_point(position) + return Geometry.is_point_in_polygon(position, polygon) func get_rect() -> Rect2: @@ -128,32 +158,69 @@ func set_rect(rect : Rect2) -> void: polygon[1] = Vector2(rect.end.x, rect.position.y) polygon[2] = rect.end polygon[3] = Vector2(rect.position.x, rect.end.y) - visible = not rect.has_no_area() +# visible = not rect.has_no_area() - var project : Project = Global.current_project - if rect.has_no_area(): - project.selected_pixels = [] - else: - project.clear_selection() - for x in range(rect.position.x, rect.end.x): - for y in range(rect.position.y, rect.end.y): - if x < 0 or x >= project.size.x: - continue - if y < 0 or y >= project.size.y: - continue - project.selected_pixels.append(Vector2(x, y)) - -func move_rect(move : Vector2) -> void: +func move_polygon(move : Vector2) -> void: _selected_rect.position += move _clipped_rect.position += move - set_rect(_selected_rect) + for i in polygon.size(): + polygon[i] += move +# set_rect(_selected_rect) + + +func move_polygon_end(new_pos : Vector2, old_pos : Vector2) -> void: + var diff := new_pos - old_pos + var selected_pixels_copy = local_selected_pixels.duplicate() + for i in selected_pixels_copy.size(): + selected_pixels_copy[i] += diff + + self.local_selected_pixels = selected_pixels_copy func select_rect() -> void: - var undo_data = _get_undo_data(false) - Global.current_project.selected_rect = _selected_rect - commit_undo("Rectangle Select", undo_data) + var project : Project = Global.current_project + self.local_selected_pixels = [] + var selected_pixels_copy = local_selected_pixels.duplicate() + for x in range(_selected_rect.position.x, _selected_rect.end.x): + for y in range(_selected_rect.position.y, _selected_rect.end.y): + var pos := Vector2(x, y) +# if polygon.size() > 4: # if it's not a rectangle +# if !Geometry.is_point_in_polygon(pos, polygon): +# continue + if x < 0 or x >= project.size.x: + continue + if y < 0 or y >= project.size.y: + continue + selected_pixels_copy.append(pos) + + self.local_selected_pixels = selected_pixels_copy + if local_selected_pixels.size() == 0: + queue_free() + return + merge_multiple_selections() +# var undo_data = _get_undo_data(false) +# Global.current_project.selected_rect = _selected_rect +# commit_undo("Rectangle Select", undo_data) + + +func merge_multiple_selections() -> void: + if Global.current_project.selections.size() < 2: + return + for selection in Global.current_project.selections: + if selection == self: + continue + var arr = Geometry.merge_polygons_2d(polygon, selection.polygon) +# print(arr) + if arr.size() == 1: # if the selections intersect + set_polygon(arr[0]) + _selected_rect = _selected_rect.merge(selection._selected_rect) + var selected_pixels_copy = local_selected_pixels.duplicate() + for pixel in selection.local_selected_pixels: + selected_pixels_copy.append(pixel) + selection.clear_selection_on_tree_exit = false + selection.queue_free() + self.local_selected_pixels = selected_pixels_copy func move_start(move_pixel : bool) -> void: @@ -283,3 +350,9 @@ func _get_undo_data(undo_image : bool) -> Dictionary: data["image_data"] = image.data image.lock() return data + + +func _on_SelectionShape_tree_exiting() -> void: + Global.current_project.selections.erase(self) + if clear_selection_on_tree_exit: + self.local_selected_pixels = [] diff --git a/src/Tools/SelectionShape.tscn b/src/Tools/SelectionShape.tscn new file mode 100644 index 000000000000..20417e45e4af --- /dev/null +++ b/src/Tools/SelectionShape.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://src/Tools/SelectionShape.gd" type="Script" id=1] + +[node name="SelectionShape" type="Polygon2D"] +z_index = 1 +color = Color( 1, 1, 1, 0 ) +invert_enable = true +invert_border = 0.5 +polygon = PoolVector2Array( 0, 0, 0, 0, 0, 0, 0, 0 ) +script = ExtResource( 1 ) +[connection signal="tree_exiting" from="." to="." method="_on_SelectionShape_tree_exiting"] diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 9607478f2d6b..c82c2c25ba09 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -265,16 +265,21 @@ func edit_menu_id_pressed(id : int) -> void: Global.current_project.undo_redo.redo() Global.control.redone = false EditMenuId.COPY: - Global.selection_rectangle.copy() + pass +# Global.selection_rectangl.copy() EditMenuId.CUT: - Global.selection_rectangle.cut() + pass +# Global.selection_rectangl.cut() EditMenuId.PASTE: - Global.selection_rectangle.paste() + pass +# Global.selection_rectangl.paste() EditMenuId.DELETE: - Global.selection_rectangle.delete() + pass +# Global.selection_rectangl.delete() EditMenuId.CLEAR_SELECTION: - Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0)) - Global.selection_rectangle.select_rect() + pass +# Global.selection_rectangl.set_rect(Rect2(0, 0, 0, 0)) +# Global.selection_rectangl.select_rect() EditMenuId.PREFERENCES: Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.dialog_open(true) diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 7ba18f57f219..6da5a8d9c293 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -7,7 +7,6 @@ [ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=5] [ext_resource path="res://src/UI/Canvas/Rulers/HorizontalRuler.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/CameraMovement.gd" type="Script" id=7] -[ext_resource path="res://src/SelectionRectangle.gd" type="Script" id=8] [ext_resource path="res://src/Shaders/TransparentChecker.shader" type="Shader" id=9] [ext_resource path="res://assets/graphics/dark_themes/tools/bucket.png" type="Texture" id=10] [ext_resource path="res://assets/graphics/dark_themes/tools/colorpicker.png" type="Texture" id=11] @@ -386,15 +385,6 @@ current = true zoom = Vector2( 0.15, 0.15 ) script = ExtResource( 7 ) -[node name="SelectionRectangle" type="Polygon2D" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer/ViewportandVerticalRuler/ViewportContainer/Viewport"] -visible = false -z_index = 1 -color = Color( 1, 1, 1, 0 ) -invert_enable = true -invert_border = 0.5 -polygon = PoolVector2Array( 0, 0, 0, 0, 0, 0, 0, 0 ) -script = ExtResource( 8 ) - [node name="ViewportContainer2" type="ViewportContainer" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer"] margin_left = 902.0 margin_right = 902.0 From 1241a4d26d5782c9449efbcc5d026937e7580393 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 14 Feb 2021 02:22:53 +0200 Subject: [PATCH 04/69] Sort vectors counter-clockwise to be used as polygon borders I did this, no idea if it works properly, probably won't be used but I thought I'd keep it saved somewhere --- src/Classes/Project.gd | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 6fe83bb83092..ddd9a7639041 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -583,6 +583,62 @@ func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 +func get_selection_polygon() -> PoolVector2Array: + if !selected_pixels: + return PoolVector2Array() + var polygon := PoolVector2Array() +# var corner = selected_pixels[0] + for pix in selected_pixels: + var polygon_point = pix + var right_n : Vector2 = pix + Vector2.RIGHT + var left_n : Vector2 = pix + Vector2.LEFT + var down_n : Vector2 = pix + Vector2.DOWN + var up_n : Vector2 = pix + Vector2.UP + + var x_axis : bool = right_n in selected_pixels and left_n in selected_pixels + var y_axis : bool = down_n in selected_pixels and up_n in selected_pixels + if x_axis or y_axis: +# if not right_n in selected_pixels and down_n + Vector2.DOWN + Vector2.RIGHT in selected_pixels: +# polygon.append(right_n + Vector2.DOWN) +# else: + continue + else: + if !(right_n in selected_pixels): + polygon_point += Vector2.RIGHT + if !(down_n in selected_pixels): + polygon_point += Vector2.DOWN + polygon.append(polygon_point) + polygon = sort_polygon(polygon) +# print(polygon) + return polygon + + +func sort_polygon(polygon : PoolVector2Array) -> PoolVector2Array: + # Find the center point of the polygon + var x := 0 + var y := 0 + for p in polygon: + x += p.x + y += p.y + var center := Vector2(x / polygon.size(), y / polygon.size()) + + # Sort points by angle from the center + var angles := [] + for p in polygon: + angles.append([p, center.angle_to_point(p)]) + angles.sort_custom(self, "sort_by_angle") + polygon.resize(0) + for p in angles: + polygon.append(p[0]) + return polygon + + +static func sort_by_angle(a, b) -> bool: + if a[1] < b[1]: + return true + return false + + func get_selection_image() -> Image: var image := Image.new() var cel_image : Image = frames[current_frame].cels[current_layer].image From ec709f6a75bc92af8aec2b8c6d68670cb12f1205 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Mon, 15 Feb 2021 23:45:44 +0200 Subject: [PATCH 05/69] More experiments I may or may not need Trying to generate a polygon from the individual selected pixels --- project.godot | 6 - src/Classes/Selection.gd | 11 -- src/Tools/SelectionShape.gd | 27 +++-- src/UI/Canvas/Selection.gd | 232 ++++++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 29 deletions(-) delete mode 100644 src/Classes/Selection.gd create mode 100644 src/UI/Canvas/Selection.gd diff --git a/project.godot b/project.godot index 69403e163fac..6337185871b4 100644 --- a/project.godot +++ b/project.godot @@ -99,11 +99,6 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/Project.gd" }, { -"base": "Reference", -"class": "Selection", -"language": "GDScript", -"path": "res://src/Classes/Selection.gd" -}, { "base": "Polygon2D", "class": "SelectionShape", "language": "GDScript", @@ -133,7 +128,6 @@ _global_script_class_icons={ "PaletteSwatch": "", "Patterns": "", "Project": "", -"Selection": "", "SelectionShape": "", "SymmetryGuide": "" } diff --git a/src/Classes/Selection.gd b/src/Classes/Selection.gd deleted file mode 100644 index 1652ba483585..000000000000 --- a/src/Classes/Selection.gd +++ /dev/null @@ -1,11 +0,0 @@ -class_name Selection extends Reference - - -var selected_area := [] # Selected pixels for each selection -var borders : PoolVector2Array -var node : SelectionShape - - -func _init(_node : SelectionShape) -> void: - node = _node - Global.canvas.add_child(node) diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index 7fa4c2c135ce..b6108fa2e501 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -23,7 +23,6 @@ func _ready() -> void: tween.start() _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) _clear_image.fill(Color(0, 0, 0, 0)) -# set_rect(Rect2(16, 20, 4, 4)) func _offset_tween_completed(_object, _key) -> void: @@ -57,17 +56,18 @@ func _draw() -> void: if !local_selected_pixels: return - var rect_pos := _selected_rect.position - var rect_end := _selected_rect.end - draw_circle(rect_pos, 1, Color.gray) - draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) - draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) - draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) - draw_circle(rect_end, 1, Color.gray) - draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) - draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) - draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) - draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) +# draw_polygon(Global.current_project.get_selection_polygon(), [Color(1, 1, 1, 0.5)]) +# var rect_pos := _selected_rect.position +# var rect_end := _selected_rect.end +# draw_circle(rect_pos, 1, Color.gray) +# draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) +# draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) +# draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) +# draw_circle(rect_end, 1, Color.gray) +# draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) +# draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) +# draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) +# draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) if _move_pixel: draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) @@ -143,6 +143,9 @@ func _local_selected_pixels_changed(value : Array) -> void: else: Global.current_project.selected_pixels.append(pixel) +# if value: +# Global.canvas.get_node("Selection").generate_polygons() + func has_point(position : Vector2) -> bool: return Geometry.is_point_in_polygon(position, polygon) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd new file mode 100644 index 000000000000..05d5235e3bb5 --- /dev/null +++ b/src/UI/Canvas/Selection.gd @@ -0,0 +1,232 @@ +extends Node2D + + +class SelectionPolygon: +# var project : Project + var border := PoolVector2Array() + var rect_outline : Rect2 + var rects := [] # Array of Rect2s + var selected_pixels := [] # Array of Vector2s - the selected pixels + + +var line_offset := Vector2.ZERO setget _offset_changed +var tween : Tween +var selection_polygons := [] # Array of SelectionPolygons + + +func _ready() -> void: + tween = Tween.new() + tween.connect("tween_completed", self, "_offset_tween_completed") + add_child(tween) + tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) + tween.start() + + +func generate_polygons(): +# selection_polygons.clear() +# selection_polygons.append(SelectionPolygon.new()) +# for pixel in Global.current_project.selected_pixels: +# var current_polygon : SelectionPolygon = selection_polygons[0] +# var rect = Rect2(pixel, Vector2.ONE) +# var pixel_border = get_rect_border(rect) +# var arr = Geometry.merge_polygons_2d(pixel_border, current_polygon.border) +## print("Arr ", arr) +# if arr.size() == 1: # if the selections intersect +## current_polygon.rects.append(rect) +# current_polygon.rect_outline.merge(rect) +# current_polygon.border = arr[0] +# +# +# return + var selected_pixels_copy := Global.current_project.selected_pixels.duplicate() + var rects := [] + while selected_pixels_copy.size() > 0: + var rect : Rect2 = generate_rect(selected_pixels_copy) + print("!!!") + if !rect: + break + for pixel in Global.current_project.selected_pixels: + if rect.has_point(pixel): +# print("POINT") + selected_pixels_copy.erase(pixel) + + rects.append(rect) + + print(rects) +# print("e ", selected_pixels_copy) + + if !rects: + return +# var polygons := [SelectionPolygon.new()] + selection_polygons.clear() + var polygons := [SelectionPolygon.new()] + var curr_polyg := 0 + + if rects.size() == 1: + polygons[0].rects.append(rects[0]) + polygons[0].rect_outline = rects[0] + polygons[0].selected_pixels = Global.current_project.selected_pixels.duplicate() + var border : PoolVector2Array = get_rect_border(rects[0]) + polygons[0].border = border + selection_polygons = polygons + return + + for i in rects.size(): + var current_polygon : SelectionPolygon = polygons[curr_polyg] + var rect : Rect2 = rects[i] + var outlines : PoolVector2Array = get_rect_border(rect) + +# var rect_prev : Rect2 = rects[i - 1] +# var outlines_prev : PoolVector2Array = get_rect_border(rect_prev) + + var arr = Geometry.merge_polygons_2d(outlines, current_polygon.border) + print("Arr ", arr) + if arr.size() == 1: # if the selections intersect + current_polygon.rects.append(rect) +# if not rect_prev in current_polygon.rects: +# current_polygon.rects.append(rect_prev) + current_polygon.rect_outline.merge(rect) + current_polygon.border = arr[0] + + selection_polygons = polygons + + +func generate_rect(pixels : Array) -> Rect2: + if !pixels: + return Rect2() + var rect := Rect2() + rect.position = pixels[0] + var reached_bottom := false + var bottom_right_corner + var p = pixels[0] + while p + Vector2.DOWN in pixels: + p += Vector2.DOWN +# while p + Vector2.RIGHT in pixels: +# p += Vector2.RIGHT + bottom_right_corner = p +# for p in pixels: +# if p + Vector2.DOWN in pixels: +# continue +# reached_bottom = true +# if p + Vector2.RIGHT in pixels: +# continue +# if reached_bottom and !bottom_right_corner: +# bottom_right_corner = p + rect.end = bottom_right_corner + Vector2.ONE + return rect + + +func get_rect_border(rect : Rect2) -> PoolVector2Array: + var border := PoolVector2Array() + border.append(rect.position) + border.append(Vector2(rect.end.x, rect.position.y)) + border.append(rect.end) + border.append(Vector2(rect.position.x, rect.end.y)) + return border + + +func _offset_tween_completed(_object, _key) -> void: + self.line_offset = Vector2.ZERO + tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) + tween.start() + + +func _offset_changed(value : Vector2) -> void: + line_offset = value + update() + + +func _draw() -> void: + for polygon in selection_polygons: + var points : PoolVector2Array = polygon.border +# print(points) + for i in range(1, points.size() + 1): + var point0 = points[i - 1] + var point1 + if i >= points.size(): + point1 = points[0] + else: + point1 = points[i] + var start_x = min(point0.x, point1.x) + var start_y = min(point0.y, point1.y) + var end_x = max(point0.x, point1.x) + var end_y = max(point0.y, point1.y) + + var start := Vector2(start_x, start_y) + var end := Vector2(end_x, end_y) + draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) + + if !polygon.selected_pixels: + return +# draw_polygon(Global.current_project.get_selection_polygon(), [Color(1, 1, 1, 0.5)]) + # var rect_pos := _selected_rect.position + # var rect_end := _selected_rect.end + # draw_circle(rect_pos, 1, Color.gray) + # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) + # draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) + # draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + # draw_circle(rect_end, 1, Color.gray) + # draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) + # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) + # draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) + # draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + +# if _move_pixel: +# draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) + + +# Taken and modified from https://github.com/juddrgledhill/godot-dashed-line +func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Color, width := 1.0, dash_length := 1.0, cap_end := false, antialiased := false) -> void: + var length = (to - from).length() + var normal = (to - from).normalized() + var dash_step = normal * dash_length + + var horizontal : bool = from.y == to.y + var _offset : Vector2 + if horizontal: + _offset = Vector2(line_offset.x, 0) + else: + _offset = Vector2(0, line_offset.y) + + if length < dash_length: # not long enough to dash + draw_line(from, to, color, width, antialiased) + return + + else: + var draw_flag = true + var segment_start = from + var steps = length/dash_length + for _start_length in range(0, steps + 1): + var segment_end = segment_start + dash_step + + var start = segment_start + _offset + start.x = min(start.x, to.x) + start.y = min(start.y, to.y) + + var end = segment_end + _offset + end.x = min(end.x, to.x) + end.y = min(end.y, to.y) + if draw_flag: + draw_line(start, end, color, width, antialiased) + else: + draw_line(start, end, color2, width, antialiased) + if _offset.length() < 1: + draw_line(from, from + _offset, color2, width, antialiased) + else: + var from_offseted : Vector2 = from + _offset + var halfway_point : Vector2 = from_offseted + if horizontal: + halfway_point += Vector2.LEFT + else: + halfway_point += Vector2.UP + + from_offseted.x = min(from_offseted.x, to.x) + from_offseted.y = min(from_offseted.y, to.y) + draw_line(halfway_point, from_offseted, color2, width, antialiased) + draw_line(from, halfway_point, color, width, antialiased) + + segment_start = segment_end + draw_flag = !draw_flag + + if cap_end: + draw_line(segment_start, to, color, width, antialiased) From bebb1b4126e580c9d44330402fd56db44d2c307f Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 16 Feb 2021 01:07:39 +0200 Subject: [PATCH 06/69] Change default rectangle select behavior and ability to clip polygons using Control --- src/Tools/RectSelect.gd | 9 +++++++-- src/Tools/SelectionShape.gd | 38 ++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index e22dad756dcc..15ffe4383219 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -17,7 +17,12 @@ func draw_start(position : Vector2) -> void: i += 1 if current_selection_id == -1: - current_selection_id = Global.current_project.selections.size() + if !Tools.shift and !Tools.control: + for selection in Global.current_project.selections: + selection.queue_free() + current_selection_id = 0 + else: + current_selection_id = Global.current_project.selections.size() var selection_shape := preload("res://src/Tools/SelectionShape.tscn").instance() Global.current_project.selections.append(selection_shape) Global.canvas.add_child(selection_shape) @@ -70,7 +75,7 @@ func draw_end(position : Vector2) -> void: _selection.move_polygon_end(position, start_position) else: var selection : SelectionShape = Global.current_project.selections[current_selection_id] - selection.select_rect() + selection.select_rect(!Tools.control) # _drag = false _move = false cursor_text = "" diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index b6108fa2e501..34b7579b4f5c 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -181,7 +181,7 @@ func move_polygon_end(new_pos : Vector2, old_pos : Vector2) -> void: self.local_selected_pixels = selected_pixels_copy -func select_rect() -> void: +func select_rect(merge := true) -> void: var project : Project = Global.current_project self.local_selected_pixels = [] var selected_pixels_copy = local_selected_pixels.duplicate() @@ -201,30 +201,38 @@ func select_rect() -> void: if local_selected_pixels.size() == 0: queue_free() return - merge_multiple_selections() + merge_multiple_selections(merge) # var undo_data = _get_undo_data(false) # Global.current_project.selected_rect = _selected_rect # commit_undo("Rectangle Select", undo_data) -func merge_multiple_selections() -> void: +func merge_multiple_selections(merge := true) -> void: if Global.current_project.selections.size() < 2: return for selection in Global.current_project.selections: if selection == self: continue - var arr = Geometry.merge_polygons_2d(polygon, selection.polygon) -# print(arr) - if arr.size() == 1: # if the selections intersect - set_polygon(arr[0]) - _selected_rect = _selected_rect.merge(selection._selected_rect) - var selected_pixels_copy = local_selected_pixels.duplicate() - for pixel in selection.local_selected_pixels: - selected_pixels_copy.append(pixel) - selection.clear_selection_on_tree_exit = false - selection.queue_free() - self.local_selected_pixels = selected_pixels_copy - + if merge: + var arr = Geometry.merge_polygons_2d(polygon, selection.polygon) + if arr.size() == 1: # if the selections intersect + set_polygon(arr[0]) + _selected_rect = _selected_rect.merge(selection._selected_rect) + var selected_pixels_copy = local_selected_pixels.duplicate() + for pixel in selection.local_selected_pixels: + selected_pixels_copy.append(pixel) + selection.clear_selection_on_tree_exit = false + selection.queue_free() + self.local_selected_pixels = selected_pixels_copy + else: + var arr = Geometry.clip_polygons_2d(selection.polygon, polygon) + if arr.size() == 1: # if the selections intersect + selection.set_polygon(arr[0]) + var selected_pixels_copy = selection.local_selected_pixels.duplicate() + for pixel in local_selected_pixels: + selected_pixels_copy.erase(pixel) + selection.local_selected_pixels = selected_pixels_copy + queue_free() func move_start(move_pixel : bool) -> void: if not move_pixel: From e2cf34e486e1758321798efb4e9111a10bba8cea Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 16 Feb 2021 01:21:56 +0200 Subject: [PATCH 07/69] Fix rectangle selection clipping --- src/Tools/SelectionShape.gd | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index 34b7579b4f5c..cc9790c9cb60 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -202,6 +202,8 @@ func select_rect(merge := true) -> void: queue_free() return merge_multiple_selections(merge) + if not merge: + queue_free() # var undo_data = _get_undo_data(false) # Global.current_project.selected_rect = _selected_rect # commit_undo("Rectangle Select", undo_data) @@ -226,13 +228,15 @@ func merge_multiple_selections(merge := true) -> void: self.local_selected_pixels = selected_pixels_copy else: var arr = Geometry.clip_polygons_2d(selection.polygon, polygon) - if arr.size() == 1: # if the selections intersect + if arr.size() == 0: # if the new selection completely overlaps the current + selection.queue_free() + elif arr.size() == 1: # if the selections intersect selection.set_polygon(arr[0]) var selected_pixels_copy = selection.local_selected_pixels.duplicate() for pixel in local_selected_pixels: selected_pixels_copy.erase(pixel) selection.local_selected_pixels = selected_pixels_copy - queue_free() + func move_start(move_pixel : bool) -> void: if not move_pixel: From d3196f72617d67e09a0f7e2c2932a81d18d7f2b4 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 16 Feb 2021 01:49:13 +0200 Subject: [PATCH 08/69] Split polygon into two with selection subtracting --- src/Tools/SelectionShape.gd | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index cc9790c9cb60..7cad8721344a 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -228,14 +228,20 @@ func merge_multiple_selections(merge := true) -> void: self.local_selected_pixels = selected_pixels_copy else: var arr = Geometry.clip_polygons_2d(selection.polygon, polygon) +# print(arr.size()) if arr.size() == 0: # if the new selection completely overlaps the current selection.queue_free() - elif arr.size() == 1: # if the selections intersect + else: # if the selections intersect selection.set_polygon(arr[0]) var selected_pixels_copy = selection.local_selected_pixels.duplicate() for pixel in local_selected_pixels: selected_pixels_copy.erase(pixel) selection.local_selected_pixels = selected_pixels_copy + for i in range(1, arr.size()): + var selection_shape = load("res://src/Tools/SelectionShape.tscn").instance() + Global.current_project.selections.append(selection_shape) + Global.canvas.add_child(selection_shape) + selection_shape.set_polygon(arr[i]) func move_start(move_pixel : bool) -> void: From d358564a6b41173b5a8b3d0a8eed29c34c6d78a2 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Mon, 22 Feb 2021 18:43:39 +0200 Subject: [PATCH 09/69] Move selection with contents with the move tool Code is still a mess, don't bother looking. --- src/Classes/Project.gd | 22 +++--- src/Tools/Move.gd | 52 ++++++-------- src/Tools/SelectionShape.gd | 139 +++++++++++++++++++++++++----------- 3 files changed, 131 insertions(+), 82 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index ddd9a7639041..bf964ffd231b 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -639,14 +639,14 @@ static func sort_by_angle(a, b) -> bool: return false -func get_selection_image() -> Image: - var image := Image.new() - var cel_image : Image = frames[current_frame].cels[current_layer].image - image.copy_from(cel_image) - image.lock() - image.fill(Color(0, 0, 0, 0)) - for pixel in selected_pixels: - var color : Color = cel_image.get_pixelv(pixel) - image.set_pixelv(pixel, color) - image.unlock() - return image +#func get_selection_image() -> Image: +# var image := Image.new() +# var cel_image : Image = frames[current_frame].cels[current_layer].image +# image.copy_from(cel_image) +# image.lock() +# image.fill(Color(0, 0, 0, 0)) +# for pixel in selected_pixels: +# var color : Color = cel_image.get_pixelv(pixel) +# image.set_pixelv(pixel, color) +# image.unlock() +# return image diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index f0ef6413657d..1ac8c1d2bb26 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -9,7 +9,8 @@ func draw_start(position : Vector2) -> void: starting_pos = position offset = position if Global.current_project.selected_pixels: - pass + for selection in Global.current_project.selections: + selection.move_content_start() # Global.selection_rectangl.move_start(true) @@ -27,34 +28,27 @@ func draw_move(position : Vector2) -> void: func draw_end(position : Vector2) -> void: if starting_pos != Vector2.INF: var pixel_diff : Vector2 = position - starting_pos - if pixel_diff != Vector2.ZERO: - var project : Project = Global.current_project - var image : Image = _get_draw_image() -# var pixels := [] -# if project.selected_pixels: -# pixels = project.selected_pixels.duplicate() -# else: -# for x in Global.current_project.size.x: -# for y in Global.current_project.size.y: -# var pos := Vector2(x, y) -# pixels.append([pos, image.get_pixelv(pos)]) - -# print(pixels[3]) - if project.selected_pixels: - for selection in Global.current_project.selections: - selection.select_rect() - else: - Global.canvas.move_preview_location = Vector2.ZERO - var image_copy := Image.new() - image_copy.copy_from(image) - Global.canvas.handle_undo("Draw") - image.fill(Color(0, 0, 0, 0)) - # image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) - image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) - # for pixel in pixels: - ## image.set_pixelv(pixel[0] + pixel_diff, Color.red) - # image.set_pixelv(pixel[0] + pixel_diff, pixel[1]) - Global.canvas.handle_redo("Draw") +# if pixel_diff != Vector2.ZERO: + var project : Project = Global.current_project + var image : Image = _get_draw_image() + + if project.selected_pixels: + pass +# for selection in Global.current_project.selections: +# selection.move_content_end() +# selection.move_polygon_end(position, starting_pos) + else: + Global.canvas.move_preview_location = Vector2.ZERO + var image_copy := Image.new() + image_copy.copy_from(image) + Global.canvas.handle_undo("Draw") + image.fill(Color(0, 0, 0, 0)) +# image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) + image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) +# for pixel in pixels: +## image.set_pixelv(pixel[0] + pixel_diff, Color.red) +# image.set_pixelv(pixel[0] + pixel_diff, pixel[1]) + Global.canvas.handle_redo("Draw") print(pixel_diff) starting_pos = Vector2.INF diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index 7cad8721344a..578c0da7a87c 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -4,6 +4,8 @@ class_name SelectionShape extends Polygon2D var line_offset := Vector2.ZERO setget _offset_changed var tween : Tween var local_selected_pixels := [] setget _local_selected_pixels_changed # Array of Vector2s +var local_image := Image.new() +var local_image_texture := ImageTexture.new() var clear_selection_on_tree_exit := true var _selected_rect := Rect2(0, 0, 0, 0) var _clipped_rect := Rect2(0, 0, 0, 0) @@ -69,8 +71,11 @@ func _draw() -> void: # draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) # draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) - if _move_pixel: - draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) + if !local_image.is_empty(): + draw_texture(local_image_texture, _selected_rect.position, Color(1, 1, 1, 0.5)) + +# if _move_pixel: +# draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) # Taken and modified from https://github.com/juddrgledhill/godot-dashed-line @@ -191,10 +196,10 @@ func select_rect(merge := true) -> void: # if polygon.size() > 4: # if it's not a rectangle # if !Geometry.is_point_in_polygon(pos, polygon): # continue - if x < 0 or x >= project.size.x: - continue - if y < 0 or y >= project.size.y: - continue +# if x < 0 or x >= project.size.x: +# continue +# if y < 0 or y >= project.size.y: +# continue selected_pixels_copy.append(pos) self.local_selected_pixels = selected_pixels_copy @@ -217,6 +222,7 @@ func merge_multiple_selections(merge := true) -> void: continue if merge: var arr = Geometry.merge_polygons_2d(polygon, selection.polygon) +# print(arr.size()) if arr.size() == 1: # if the selections intersect set_polygon(arr[0]) _selected_rect = _selected_rect.merge(selection._selected_rect) @@ -228,7 +234,6 @@ func merge_multiple_selections(merge := true) -> void: self.local_selected_pixels = selected_pixels_copy else: var arr = Geometry.clip_polygons_2d(selection.polygon, polygon) -# print(arr.size()) if arr.size() == 0: # if the new selection completely overlaps the current selection.queue_free() else: # if the selections intersect @@ -244,44 +249,93 @@ func merge_multiple_selections(merge := true) -> void: selection_shape.set_polygon(arr[i]) -func move_start(move_pixel : bool) -> void: - if not move_pixel: - return - - _undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - - var rect = Rect2(Vector2.ZERO, project.size) - _clipped_rect = rect.clip(_selected_rect) - _move_image = image.get_rect(_clipped_rect) - _move_texture.create_from_image(_move_image, 0) +func get_image() -> Image: + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + var image := Image.new() + image = cel_image.get_rect(_selected_rect) +# print(polygon.size()) + if polygon.size() > 4: + image.lock() + var image_pixel := Vector2.ZERO + for x in range(_selected_rect.position.x, _selected_rect.end.x): + image_pixel.y = 0 + for y in range(_selected_rect.position.y, _selected_rect.end.y): + var pos := Vector2(x, y) +# if not Geometry.is_point_in_polygon(pos, polygon): + if not pos in local_selected_pixels: + # print(pixel) + image.set_pixelv(image_pixel, Color(0, 0, 0, 0)) + image_pixel.y += 1 + image_pixel.x += 1 - var size := _clipped_rect.size - rect = Rect2(Vector2.ZERO, size) - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(_clear_image, rect, _clipped_rect.position) - Global.canvas.update_texture(project.current_layer) + image.unlock() +# image.create(_selected_rect.size.x, _selected_rect.size.y, false, Image.FORMAT_RGBA8) - _move_pixel = true - update() + return image -func move_end() -> void: - var undo_data = _undo_data if _move_pixel else _get_undo_data(false) +func move_content_start() -> void: + if !local_image.is_empty(): + return + local_image = get_image() + local_image_texture.create_from_image(local_image, 0) + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + _clear_image.resize(_selected_rect.size.x, _selected_rect.size.y, Image.INTERPOLATE_NEAREST) + cel_image.blit_rect_mask(_clear_image, local_image, Rect2(Vector2.ZERO, _selected_rect.size), _selected_rect.position) + Global.canvas.update_texture(project.current_layer) - if _move_pixel: - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _clipped_rect.size - var rect = Rect2(Vector2.ZERO, size) - image.blit_rect_mask(_move_image, _move_image, rect, _clipped_rect.position) - _move_pixel = false - update() - Global.current_project.selected_rect = _selected_rect - commit_undo("Rectangle Select", undo_data) - _undo_data.clear() +func move_content_end() -> void: + if local_image.is_empty(): + return + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(local_image, local_image, Rect2(Vector2.ZERO, _selected_rect.size), _selected_rect.position) + Global.canvas.update_texture(project.current_layer) + local_image = Image.new() + local_image_texture = ImageTexture.new() + + +#func move_start(move_pixel : bool) -> void: +# if not move_pixel: +# return +# +# _undo_data = _get_undo_data(true) +# var project := Global.current_project +# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image +# +# var rect = Rect2(Vector2.ZERO, project.size) +# _clipped_rect = rect.clip(_selected_rect) +# _move_image = image.get_rect(_clipped_rect) +# _move_texture.create_from_image(_move_image, 0) +# +# var size := _clipped_rect.size +# rect = Rect2(Vector2.ZERO, size) +# _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) +# image.blit_rect(_clear_image, rect, _clipped_rect.position) +# Global.canvas.update_texture(project.current_layer) +# +# _move_pixel = true +# update() +# +# +#func move_end() -> void: +# var undo_data = _undo_data if _move_pixel else _get_undo_data(false) +# +# if _move_pixel: +# var project := Global.current_project +# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image +# var size := _clipped_rect.size +# var rect = Rect2(Vector2.ZERO, size) +# image.blit_rect_mask(_move_image, _move_image, rect, _clipped_rect.position) +# _move_pixel = false +# update() +# +# Global.current_project.selected_rect = _selected_rect +# commit_undo("Rectangle Select", undo_data) +# _undo_data.clear() func copy() -> void: @@ -314,7 +368,7 @@ func cut() -> void: # This is basically the same as copy + delete var brush = _clipboard.get_rect(_clipboard.get_used_rect()) project.brushes.append(brush) Brushes.add_project_brush(brush) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning +# move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning image.blit_rect(_clear_image, rect, _selected_rect.position) commit_undo("Draw", undo_data) @@ -328,7 +382,7 @@ func paste() -> void: var size := _selected_rect.size var rect = Rect2(Vector2.ZERO, size) image.blend_rect(_clipboard, rect, _selected_rect.position) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning +# move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning commit_undo("Draw", undo_data) @@ -340,7 +394,7 @@ func delete() -> void: var rect = Rect2(Vector2.ZERO, size) _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) image.blit_rect(_clear_image, rect, _selected_rect.position) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning +# move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning commit_undo("Draw", undo_data) @@ -374,6 +428,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: func _on_SelectionShape_tree_exiting() -> void: + move_content_end() Global.current_project.selections.erase(self) if clear_selection_on_tree_exit: self.local_selected_pixels = [] From ac057dbf361dba69df5c5363d3671578576ec78c Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 23 Feb 2021 04:05:56 +0200 Subject: [PATCH 10/69] Move some methods from SelectionShape.gd to Selection.gd The purpose of this is to generalize some selection code, so that it applies to all polygons, the entire selection. More will follow. --- src/Tools/Move.gd | 11 +- src/Tools/RectSelect.gd | 17 +-- src/Tools/SelectionShape.gd | 44 +----- src/UI/Canvas/Canvas.gd | 3 +- src/UI/Canvas/Canvas.tscn | 6 +- src/UI/Canvas/Selection.gd | 297 +++++++++++++----------------------- 6 files changed, 126 insertions(+), 252 deletions(-) diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index 1ac8c1d2bb26..b1ba16992905 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -9,15 +9,13 @@ func draw_start(position : Vector2) -> void: starting_pos = position offset = position if Global.current_project.selected_pixels: - for selection in Global.current_project.selections: - selection.move_content_start() + Global.canvas.selection.move_content_start() # Global.selection_rectangl.move_start(true) func draw_move(position : Vector2) -> void: if Global.current_project.selected_pixels: - for selection in Global.current_project.selections: - selection.move_polygon(position - offset) + Global.canvas.selection.move_borders(position - offset) offset = position # Global.selection_rectangl.move_rect(position - offset) else: @@ -34,9 +32,8 @@ func draw_end(position : Vector2) -> void: if project.selected_pixels: pass -# for selection in Global.current_project.selections: -# selection.move_content_end() -# selection.move_polygon_end(position, starting_pos) +# Global.canvas.selection.move_content_end() +# Global.canvas.selection.move_borders_end(position, starting_pos) else: Global.canvas.move_preview_location = Vector2.ZERO var image_copy := Image.new() diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 15ffe4383219..20112c2d8ce0 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -25,7 +25,7 @@ func draw_start(position : Vector2) -> void: current_selection_id = Global.current_project.selections.size() var selection_shape := preload("res://src/Tools/SelectionShape.tscn").instance() Global.current_project.selections.append(selection_shape) - Global.canvas.add_child(selection_shape) + Global.canvas.selection.add_child(selection_shape) _start = Rect2(position, Vector2.ZERO) selection_shape.set_rect(_start) else: @@ -49,8 +49,7 @@ func draw_move(position : Vector2) -> void: var selection : SelectionShape = Global.current_project.selections[current_selection_id] if _move: - for _selection in Global.current_project.selections: - _selection.move_polygon(position - _offset) + Global.canvas.selection.move_borders(position - _offset) _offset = position _set_cursor_text(selection.get_rect()) else: @@ -58,21 +57,11 @@ func draw_move(position : Vector2) -> void: rect = rect.grow_individual(0, 0, 1, 1) selection.set_rect(rect) _set_cursor_text(rect) -# if _move: -# Global.selection_rectangle.move_rect(position - _offset) -# _offset = position -# _set_cursor_text(Global.selection_rectangle.get_rect()) -# else: -# var rect := _start.expand(position).abs() -# rect = rect.grow_individual(0, 0, 1, 1) -# Global.selection_rectangle.set_rect(rect) -# _set_cursor_text(rect) func draw_end(position : Vector2) -> void: if _move: - for _selection in Global.current_project.selections: - _selection.move_polygon_end(position, start_position) + Global.canvas.selection.move_borders_end(position, start_position) else: var selection : SelectionShape = Global.current_project.selections[current_selection_id] selection.select_rect(!Tools.control) diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index 578c0da7a87c..5a2daee6bb85 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -169,23 +169,6 @@ func set_rect(rect : Rect2) -> void: # visible = not rect.has_no_area() -func move_polygon(move : Vector2) -> void: - _selected_rect.position += move - _clipped_rect.position += move - for i in polygon.size(): - polygon[i] += move -# set_rect(_selected_rect) - - -func move_polygon_end(new_pos : Vector2, old_pos : Vector2) -> void: - var diff := new_pos - old_pos - var selected_pixels_copy = local_selected_pixels.duplicate() - for i in selected_pixels_copy.size(): - selected_pixels_copy[i] += diff - - self.local_selected_pixels = selected_pixels_copy - - func select_rect(merge := true) -> void: var project : Project = Global.current_project self.local_selected_pixels = [] @@ -245,7 +228,7 @@ func merge_multiple_selections(merge := true) -> void: for i in range(1, arr.size()): var selection_shape = load("res://src/Tools/SelectionShape.tscn").instance() Global.current_project.selections.append(selection_shape) - Global.canvas.add_child(selection_shape) + Global.canvas.selection.add_child(selection_shape) selection_shape.set_polygon(arr[i]) @@ -275,29 +258,6 @@ func get_image() -> Image: return image -func move_content_start() -> void: - if !local_image.is_empty(): - return - local_image = get_image() - local_image_texture.create_from_image(local_image, 0) - var project : Project = Global.current_project - var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - _clear_image.resize(_selected_rect.size.x, _selected_rect.size.y, Image.INTERPOLATE_NEAREST) - cel_image.blit_rect_mask(_clear_image, local_image, Rect2(Vector2.ZERO, _selected_rect.size), _selected_rect.position) - Global.canvas.update_texture(project.current_layer) - - -func move_content_end() -> void: - if local_image.is_empty(): - return - var project : Project = Global.current_project - var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(local_image, local_image, Rect2(Vector2.ZERO, _selected_rect.size), _selected_rect.position) - Global.canvas.update_texture(project.current_layer) - local_image = Image.new() - local_image_texture = ImageTexture.new() - - #func move_start(move_pixel : bool) -> void: # if not move_pixel: # return @@ -428,7 +388,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: func _on_SelectionShape_tree_exiting() -> void: - move_content_end() + get_parent().move_content_end() Global.current_project.selections.erase(self) if clear_selection_on_tree_exit: self.local_selected_pixels = [] diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index b90140d52eec..74e759149a7b 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -11,9 +11,10 @@ var move_preview_location := Vector2.ZERO onready var currently_visible_frame : Viewport = $CurrentlyVisibleFrame onready var current_frame_drawer = $CurrentlyVisibleFrame/CurrentFrameDrawer +onready var tile_mode = $TileMode onready var pixel_grid = $PixelGrid onready var grid = $Grid -onready var tile_mode = $TileMode +onready var selection = $Selection onready var indicators = $Indicators onready var previews = $Previews diff --git a/src/UI/Canvas/Canvas.tscn b/src/UI/Canvas/Canvas.tscn index 75e266ffa214..cc46ac1da9ab 100644 --- a/src/UI/Canvas/Canvas.tscn +++ b/src/UI/Canvas/Canvas.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=2] +[gd_scene load_steps=10 format=2] [ext_resource path="res://src/UI/Canvas/Canvas.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/Grid.gd" type="Script" id=2] @@ -7,6 +7,7 @@ [ext_resource path="res://src/UI/Canvas/CurrentFrameDrawer.gd" type="Script" id=5] [ext_resource path="res://src/UI/Canvas/PixelGrid.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/Previews.gd" type="Script" id=7] +[ext_resource path="res://src/UI/Canvas/Selection.gd" type="Script" id=8] [sub_resource type="CanvasItemMaterial" id=1] blend_mode = 4 @@ -36,6 +37,9 @@ script = ExtResource( 6 ) [node name="Grid" type="Node2D" parent="."] script = ExtResource( 2 ) +[node name="Selection" type="Node2D" parent="."] +script = ExtResource( 7 ) + [node name="Indicators" type="Node2D" parent="."] script = ExtResource( 3 ) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 05d5235e3bb5..5344e9eddf0c 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -1,94 +1,55 @@ extends Node2D -class SelectionPolygon: -# var project : Project - var border := PoolVector2Array() - var rect_outline : Rect2 - var rects := [] # Array of Rect2s - var selected_pixels := [] # Array of Vector2s - the selected pixels +#class SelectionPolygon: +## var project : Project +# var border := PoolVector2Array() +# var rect_outline : Rect2 +# var rects := [] # Array of Rect2s +# var selected_pixels := [] # Array of Vector2s - the selected pixels -var line_offset := Vector2.ZERO setget _offset_changed -var tween : Tween -var selection_polygons := [] # Array of SelectionPolygons +func move_borders(move : Vector2) -> void: + for shape in get_children(): + shape._selected_rect.position += move + shape._clipped_rect.position += move + for i in shape.polygon.size(): + shape.polygon[i] += move -func _ready() -> void: - tween = Tween.new() - tween.connect("tween_completed", self, "_offset_tween_completed") - add_child(tween) - tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) - tween.start() +func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: + for shape in get_children(): + var diff := new_pos - old_pos + var selected_pixels_copy = shape.local_selected_pixels.duplicate() + for i in selected_pixels_copy.size(): + selected_pixels_copy[i] += diff + shape.local_selected_pixels = selected_pixels_copy -func generate_polygons(): -# selection_polygons.clear() -# selection_polygons.append(SelectionPolygon.new()) -# for pixel in Global.current_project.selected_pixels: -# var current_polygon : SelectionPolygon = selection_polygons[0] -# var rect = Rect2(pixel, Vector2.ONE) -# var pixel_border = get_rect_border(rect) -# var arr = Geometry.merge_polygons_2d(pixel_border, current_polygon.border) -## print("Arr ", arr) -# if arr.size() == 1: # if the selections intersect -## current_polygon.rects.append(rect) -# current_polygon.rect_outline.merge(rect) -# current_polygon.border = arr[0] -# -# -# return - var selected_pixels_copy := Global.current_project.selected_pixels.duplicate() - var rects := [] - while selected_pixels_copy.size() > 0: - var rect : Rect2 = generate_rect(selected_pixels_copy) - print("!!!") - if !rect: - break - for pixel in Global.current_project.selected_pixels: - if rect.has_point(pixel): -# print("POINT") - selected_pixels_copy.erase(pixel) - - rects.append(rect) - - print(rects) -# print("e ", selected_pixels_copy) - - if !rects: - return -# var polygons := [SelectionPolygon.new()] - selection_polygons.clear() - var polygons := [SelectionPolygon.new()] - var curr_polyg := 0 - - if rects.size() == 1: - polygons[0].rects.append(rects[0]) - polygons[0].rect_outline = rects[0] - polygons[0].selected_pixels = Global.current_project.selected_pixels.duplicate() - var border : PoolVector2Array = get_rect_border(rects[0]) - polygons[0].border = border - selection_polygons = polygons - return - - for i in rects.size(): - var current_polygon : SelectionPolygon = polygons[curr_polyg] - var rect : Rect2 = rects[i] - var outlines : PoolVector2Array = get_rect_border(rect) - -# var rect_prev : Rect2 = rects[i - 1] -# var outlines_prev : PoolVector2Array = get_rect_border(rect_prev) - - var arr = Geometry.merge_polygons_2d(outlines, current_polygon.border) - print("Arr ", arr) - if arr.size() == 1: # if the selections intersect - current_polygon.rects.append(rect) -# if not rect_prev in current_polygon.rects: -# current_polygon.rects.append(rect_prev) - current_polygon.rect_outline.merge(rect) - current_polygon.border = arr[0] - - selection_polygons = polygons + +func move_content_start() -> void: + for shape in get_children(): + if !shape.local_image.is_empty(): + return + shape.local_image = shape.get_image() + shape.local_image_texture.create_from_image(shape.local_image, 0) + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + shape._clear_image.resize(shape._selected_rect.size.x, shape._selected_rect.size.y, Image.INTERPOLATE_NEAREST) + cel_image.blit_rect_mask(shape._clear_image, shape.local_image, Rect2(Vector2.ZERO, shape._selected_rect.size), shape._selected_rect.position) + Global.canvas.update_texture(project.current_layer) + + +func move_content_end() -> void: + for shape in get_children(): + if shape.local_image.is_empty(): + return + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(shape.local_image, shape.local_image, Rect2(Vector2.ZERO, shape._selected_rect.size), shape._selected_rect.position) + Global.canvas.update_texture(project.current_layer) + shape.local_image = Image.new() + shape.local_image_texture = ImageTexture.new() func generate_rect(pixels : Array) -> Rect2: @@ -125,108 +86,70 @@ func get_rect_border(rect : Rect2) -> PoolVector2Array: return border -func _offset_tween_completed(_object, _key) -> void: - self.line_offset = Vector2.ZERO - tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) - tween.start() - - -func _offset_changed(value : Vector2) -> void: - line_offset = value - update() - - -func _draw() -> void: - for polygon in selection_polygons: - var points : PoolVector2Array = polygon.border -# print(points) - for i in range(1, points.size() + 1): - var point0 = points[i - 1] - var point1 - if i >= points.size(): - point1 = points[0] - else: - point1 = points[i] - var start_x = min(point0.x, point1.x) - var start_y = min(point0.y, point1.y) - var end_x = max(point0.x, point1.x) - var end_y = max(point0.y, point1.y) - - var start := Vector2(start_x, start_y) - var end := Vector2(end_x, end_y) - draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) - - if !polygon.selected_pixels: - return -# draw_polygon(Global.current_project.get_selection_polygon(), [Color(1, 1, 1, 0.5)]) - # var rect_pos := _selected_rect.position - # var rect_end := _selected_rect.end - # draw_circle(rect_pos, 1, Color.gray) - # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) - # draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) - # draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) - # draw_circle(rect_end, 1, Color.gray) - # draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) - # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) - # draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) - # draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) - -# if _move_pixel: -# draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) - - -# Taken and modified from https://github.com/juddrgledhill/godot-dashed-line -func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Color, width := 1.0, dash_length := 1.0, cap_end := false, antialiased := false) -> void: - var length = (to - from).length() - var normal = (to - from).normalized() - var dash_step = normal * dash_length - - var horizontal : bool = from.y == to.y - var _offset : Vector2 - if horizontal: - _offset = Vector2(line_offset.x, 0) - else: - _offset = Vector2(0, line_offset.y) - - if length < dash_length: # not long enough to dash - draw_line(from, to, color, width, antialiased) - return - - else: - var draw_flag = true - var segment_start = from - var steps = length/dash_length - for _start_length in range(0, steps + 1): - var segment_end = segment_start + dash_step - - var start = segment_start + _offset - start.x = min(start.x, to.x) - start.y = min(start.y, to.y) - - var end = segment_end + _offset - end.x = min(end.x, to.x) - end.y = min(end.y, to.y) - if draw_flag: - draw_line(start, end, color, width, antialiased) - else: - draw_line(start, end, color2, width, antialiased) - if _offset.length() < 1: - draw_line(from, from + _offset, color2, width, antialiased) - else: - var from_offseted : Vector2 = from + _offset - var halfway_point : Vector2 = from_offseted - if horizontal: - halfway_point += Vector2.LEFT - else: - halfway_point += Vector2.UP - - from_offseted.x = min(from_offseted.x, to.x) - from_offseted.y = min(from_offseted.y, to.y) - draw_line(halfway_point, from_offseted, color2, width, antialiased) - draw_line(from, halfway_point, color, width, antialiased) - - segment_start = segment_end - draw_flag = !draw_flag - - if cap_end: - draw_line(segment_start, to, color, width, antialiased) +#func generate_polygons(): +## selection_polygons.clear() +## selection_polygons.append(SelectionPolygon.new()) +## for pixel in Global.current_project.selected_pixels: +## var current_polygon : SelectionPolygon = selection_polygons[0] +## var rect = Rect2(pixel, Vector2.ONE) +## var pixel_border = get_rect_border(rect) +## var arr = Geometry.merge_polygons_2d(pixel_border, current_polygon.border) +### print("Arr ", arr) +## if arr.size() == 1: # if the selections intersect +### current_polygon.rects.append(rect) +## current_polygon.rect_outline.merge(rect) +## current_polygon.border = arr[0] +## +## +## return +# var selected_pixels_copy := Global.current_project.selected_pixels.duplicate() +# var rects := [] +# while selected_pixels_copy.size() > 0: +# var rect : Rect2 = generate_rect(selected_pixels_copy) +# print("!!!") +# if !rect: +# break +# for pixel in Global.current_project.selected_pixels: +# if rect.has_point(pixel): +## print("POINT") +# selected_pixels_copy.erase(pixel) +# +# rects.append(rect) +# +# print(rects) +## print("e ", selected_pixels_copy) +# +# if !rects: +# return +## var polygons := [SelectionPolygon.new()] +# selection_polygons.clear() +# var polygons := [SelectionPolygon.new()] +# var curr_polyg := 0 +# +# if rects.size() == 1: +# polygons[0].rects.append(rects[0]) +# polygons[0].rect_outline = rects[0] +# polygons[0].selected_pixels = Global.current_project.selected_pixels.duplicate() +# var border : PoolVector2Array = get_rect_border(rects[0]) +# polygons[0].border = border +# selection_polygons = polygons +# return +# +# for i in rects.size(): +# var current_polygon : SelectionPolygon = polygons[curr_polyg] +# var rect : Rect2 = rects[i] +# var outlines : PoolVector2Array = get_rect_border(rect) +# +## var rect_prev : Rect2 = rects[i - 1] +## var outlines_prev : PoolVector2Array = get_rect_border(rect_prev) +# +# var arr = Geometry.merge_polygons_2d(outlines, current_polygon.border) +# print("Arr ", arr) +# if arr.size() == 1: # if the selections intersect +# current_polygon.rects.append(rect) +## if not rect_prev in current_polygon.rects: +## current_polygon.rects.append(rect_prev) +# current_polygon.rect_outline.merge(rect) +# current_polygon.border = arr[0] +# +# selection_polygons = polygons From e2c3df22b4f9dfb7880ee179a517462b9dd5cfde Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 25 Feb 2021 01:38:00 +0200 Subject: [PATCH 11/69] UndoRedo for border moving Nothing else in the selections system works properly in UndoRedo right now. Needs: - UR support for creating selections - UR support for modifying selections (merging and cutting selections together) - UR support for removing selection - UR support for moving content & for all the rest of the remaining features --- src/Tools/Move.gd | 1 - src/Tools/RectSelect.gd | 1 + src/Tools/SelectionShape.gd | 126 +++++++++++++++--------------------- src/UI/Canvas/Selection.gd | 59 ++++++++++++++++- 4 files changed, 110 insertions(+), 77 deletions(-) diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index b1ba16992905..d1b33e929ade 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -10,7 +10,6 @@ func draw_start(position : Vector2) -> void: offset = position if Global.current_project.selected_pixels: Global.canvas.selection.move_content_start() -# Global.selection_rectangl.move_start(true) func draw_move(position : Vector2) -> void: diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 20112c2d8ce0..04d0533624cb 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -33,6 +33,7 @@ func draw_start(position : Vector2) -> void: _move = true _offset = position start_position = position + Global.canvas.selection.move_borders_start() _set_cursor_text(selection.get_rect()) # if Global.selection_rectangle.has_point(position): # _move = true diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd index 5a2daee6bb85..c0781a50a71c 100644 --- a/src/Tools/SelectionShape.gd +++ b/src/Tools/SelectionShape.gd @@ -7,8 +7,8 @@ var local_selected_pixels := [] setget _local_selected_pixels_changed # Array of var local_image := Image.new() var local_image_texture := ImageTexture.new() var clear_selection_on_tree_exit := true +var temp_polygon : PoolVector2Array setget _temp_polygon_changed var _selected_rect := Rect2(0, 0, 0, 0) -var _clipped_rect := Rect2(0, 0, 0, 0) var _move_image := Image.new() var _move_texture := ImageTexture.new() var _clear_image := Image.new() @@ -152,6 +152,11 @@ func _local_selected_pixels_changed(value : Array) -> void: # Global.canvas.get_node("Selection").generate_polygons() +func _temp_polygon_changed(value : PoolVector2Array) -> void: + temp_polygon = value + polygon = temp_polygon + + func has_point(position : Vector2) -> bool: return Geometry.is_point_in_polygon(position, polygon) @@ -311,80 +316,51 @@ func copy() -> void: project.brushes.append(brush) Brushes.add_project_brush(brush) -func cut() -> void: # This is basically the same as copy + delete - if _selected_rect.has_no_area(): - return - - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - _clipboard = image.get_rect(_selected_rect) - if _clipboard.is_invisible(): - return - - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - var brush = _clipboard.get_rect(_clipboard.get_used_rect()) - project.brushes.append(brush) - Brushes.add_project_brush(brush) -# move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - image.blit_rect(_clear_image, rect, _selected_rect.position) - commit_undo("Draw", undo_data) - -func paste() -> void: - if _clipboard.get_size() <= Vector2.ZERO: - return - - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - image.blend_rect(_clipboard, rect, _selected_rect.position) -# move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - commit_undo("Draw", undo_data) - - -func delete() -> void: - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(_clear_image, rect, _selected_rect.position) -# move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - commit_undo("Draw", undo_data) - - -func commit_undo(action : String, undo_data : Dictionary) -> void: - var redo_data = _get_undo_data("image_data" in undo_data) - var project := Global.current_project - - project.undos += 1 - project.undo_redo.create_action(action) - project.undo_redo.add_do_property(project, "selected_rect", redo_data["selected_rect"]) - project.undo_redo.add_undo_property(project, "selected_rect", undo_data["selected_rect"]) - if "image_data" in undo_data: - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) - project.undo_redo.add_undo_property(image, "data", undo_data["image_data"]) - project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) - project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) - project.undo_redo.commit_action() - - -func _get_undo_data(undo_image : bool) -> Dictionary: - var data = {} - var project := Global.current_project - data["selected_rect"] = Global.current_project.selected_rect - if undo_image: - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - image.unlock() - data["image_data"] = image.data - image.lock() - return data +#func cut() -> void: # This is basically the same as copy + delete +# if _selected_rect.has_no_area(): +# return +# +# var undo_data = _get_undo_data(true) +# var project := Global.current_project +# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image +# var size := _selected_rect.size +# var rect = Rect2(Vector2.ZERO, size) +# _clipboard = image.get_rect(_selected_rect) +# if _clipboard.is_invisible(): +# return +# +# _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) +# var brush = _clipboard.get_rect(_clipboard.get_used_rect()) +# project.brushes.append(brush) +# Brushes.add_project_brush(brush) +## move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning +# image.blit_rect(_clear_image, rect, _selected_rect.position) +# commit_undo("Draw", undo_data) +# +#func paste() -> void: +# if _clipboard.get_size() <= Vector2.ZERO: +# return +# +# var undo_data = _get_undo_data(true) +# var project := Global.current_project +# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image +# var size := _selected_rect.size +# var rect = Rect2(Vector2.ZERO, size) +# image.blend_rect(_clipboard, rect, _selected_rect.position) +## move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning +# commit_undo("Draw", undo_data) +# +# +#func delete() -> void: +# var undo_data = _get_undo_data(true) +# var project := Global.current_project +# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image +# var size := _selected_rect.size +# var rect = Rect2(Vector2.ZERO, size) +# _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) +# image.blit_rect(_clear_image, rect, _selected_rect.position) +## move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning +# commit_undo("Draw", undo_data) func _on_SelectionShape_tree_exiting() -> void: diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 5344e9eddf0c..611881adaa7c 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -9,25 +9,37 @@ extends Node2D # var selected_pixels := [] # Array of Vector2s - the selected pixels +func move_borders_start() -> void: + for shape in get_children(): + shape.temp_polygon = shape.polygon + + func move_borders(move : Vector2) -> void: for shape in get_children(): shape._selected_rect.position += move - shape._clipped_rect.position += move for i in shape.polygon.size(): shape.polygon[i] += move func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: +# for shape in get_children(): +# var diff := new_pos - old_pos +# for i in shape.polygon.size(): +# shape.polygon[i] -= diff # Temporarily set the polygon back to be used for undoredo + var undo_data = _get_undo_data(false) for shape in get_children(): + shape.temp_polygon = shape.polygon var diff := new_pos - old_pos var selected_pixels_copy = shape.local_selected_pixels.duplicate() for i in selected_pixels_copy.size(): selected_pixels_copy[i] += diff shape.local_selected_pixels = selected_pixels_copy + commit_undo("Rectangle Select", undo_data) func move_content_start() -> void: + move_borders_start() for shape in get_children(): if !shape.local_image.is_empty(): return @@ -52,6 +64,51 @@ func move_content_end() -> void: shape.local_image_texture = ImageTexture.new() +func commit_undo(action : String, undo_data : Dictionary) -> void: + var redo_data = _get_undo_data("image_data" in undo_data) + var project := Global.current_project + + project.undos += 1 + project.undo_redo.create_action(action) + var i := 0 + for shape in get_children(): + project.undo_redo.add_do_property(shape, "temp_polygon", redo_data["temp_polygon_%s" % i]) + project.undo_redo.add_do_property(shape, "_selected_rect", redo_data["_selected_rect_%s" % i]) + project.undo_redo.add_do_property(shape, "local_selected_pixels", redo_data["local_selected_pixels_%s" % i]) + project.undo_redo.add_undo_property(shape, "temp_polygon", undo_data["temp_polygon_%s" % i]) + project.undo_redo.add_undo_property(shape, "_selected_rect", undo_data["_selected_rect_%s" % i]) + project.undo_redo.add_undo_property(shape, "local_selected_pixels", undo_data["local_selected_pixels_%s" % i]) + i += 1 + + if "image_data" in undo_data: + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) + project.undo_redo.add_undo_property(image, "data", undo_data["image_data"]) + project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) + project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) + project.undo_redo.commit_action() + + +func _get_undo_data(undo_image : bool) -> Dictionary: + var data = {} + var project := Global.current_project + var i := 0 + for shape in get_children(): + data["temp_polygon_%s" % i] = shape.temp_polygon + data["_selected_rect_%s" % i] = shape._selected_rect + data["local_selected_pixels_%s" % i] = shape.local_selected_pixels + i += 1 +# data["selected_rect"] = Global.current_project.selected_rect + if undo_image: + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + image.unlock() + data["image_data"] = image.data + image.lock() + for d in data.keys(): + print(d, data[d]) + return data + + func generate_rect(pixels : Array) -> Rect2: if !pixels: return Rect2() From d0709c84d2cc5b4e1b4abfc4fcc0f5c43d613a48 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 2 Mar 2021 03:54:15 +0200 Subject: [PATCH 12/69] Moving all of the selection shape logic to Selection.gd Handle all of the polygons there instead of having them as individual nodes. Should be easier to handle undo/redo this way. This commit probably breaks move tool + selection tool and undo/redo. Code is still a mess. For your sanity, I hope you are not reading this. I promise I will clean up. --- src/Tools/RectSelect.gd | 54 +++---- src/UI/Canvas/Selection.gd | 296 +++++++++++++++++++++++++++++++++---- 2 files changed, 297 insertions(+), 53 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 04d0533624cb..3dda7fbca6ba 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -1,7 +1,7 @@ extends BaseTool -var current_selection_id := -1 +var current_selection var start_position := Vector2.INF var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO @@ -10,31 +10,34 @@ var _move := false func draw_start(position : Vector2) -> void: - var i := 0 - for selection in Global.current_project.selections: - if selection.has_point(position): - current_selection_id = i - i += 1 + for selection in Global.canvas.selection.polygons: + if selection.rect_outline.has_point(position): + current_selection = selection - if current_selection_id == -1: + if !current_selection: if !Tools.shift and !Tools.control: - for selection in Global.current_project.selections: - selection.queue_free() - current_selection_id = 0 - else: - current_selection_id = Global.current_project.selections.size() - var selection_shape := preload("res://src/Tools/SelectionShape.tscn").instance() - Global.current_project.selections.append(selection_shape) - Global.canvas.selection.add_child(selection_shape) + Global.canvas.selection.polygons.clear() _start = Rect2(position, Vector2.ZERO) - selection_shape.set_rect(_start) + var new_selection = Global.canvas.selection.SelectionPolygon.new(_start) + Global.canvas.selection.polygons.append(new_selection) + current_selection = new_selection +# for selection in Global.current_project.selections: +# selection.queue_free() +# current_selection_id = 0 +# else: +# current_selection_id = Global.current_project.selections.size() +# var selection_shape := preload("res://src/Tools/SelectionShape.tscn").instance() +# current_selection = selection_shape +## Global.current_project.selections.append(selection_shape) +# Global.canvas.selection.add_child(selection_shape) +# _start = Rect2(position, Vector2.ZERO) +# selection_shape.set_rect(_start) else: - var selection : SelectionShape = Global.current_project.selections[current_selection_id] _move = true _offset = position start_position = position Global.canvas.selection.move_borders_start() - _set_cursor_text(selection.get_rect()) +# _set_cursor_text(selection.get_rect()) # if Global.selection_rectangle.has_point(position): # _move = true # _offset = position @@ -47,16 +50,14 @@ func draw_start(position : Vector2) -> void: func draw_move(position : Vector2) -> void: - var selection : SelectionShape = Global.current_project.selections[current_selection_id] - if _move: Global.canvas.selection.move_borders(position - _offset) _offset = position - _set_cursor_text(selection.get_rect()) +# _set_cursor_text(selection.get_rect()) else: var rect := _start.expand(position).abs() rect = rect.grow_individual(0, 0, 1, 1) - selection.set_rect(rect) + current_selection.set_rect(rect) _set_cursor_text(rect) @@ -64,13 +65,16 @@ func draw_end(position : Vector2) -> void: if _move: Global.canvas.selection.move_borders_end(position, start_position) else: - var selection : SelectionShape = Global.current_project.selections[current_selection_id] - selection.select_rect(!Tools.control) + Global.canvas.selection.select_rect(!Tools.control) +# var undo_data = Global.canvas.selection._get_undo_data(false) +# current_selection.select_rect(!Tools.control) +# Global.canvas.selection.commit_undo("Rectangle Select", undo_data) # _drag = false +# Global.canvas.selection.move_borders_end(position, start_position) _move = false cursor_text = "" start_position = Vector2.INF - current_selection_id = -1 + current_selection = null func cursor_move(_position : Vector2) -> void: diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 611881adaa7c..ec36da8dcd01 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -1,24 +1,79 @@ extends Node2D -#class SelectionPolygon: -## var project : Project -# var border := PoolVector2Array() -# var rect_outline : Rect2 +class SelectionPolygon: +# var project : Project + var border := PoolVector2Array() + var rect_outline : Rect2 # var rects := [] # Array of Rect2s -# var selected_pixels := [] # Array of Vector2s - the selected pixels + var selected_pixels := [] setget _selected_pixels_changed # Array of Vector2s - the selected pixels + + + func _init(rect : Rect2) -> void: + rect_outline = rect + border.append(rect.position) + border.append(Vector2(rect.end.x, rect.position.y)) + border.append(rect.end) + border.append(Vector2(rect.position.x, rect.end.y)) + + + func set_rect(rect : Rect2) -> void: + rect_outline = rect + border[0] = rect.position + border[1] = Vector2(rect.end.x, rect.position.y) + border[2] = rect.end + border[3] = Vector2(rect.position.x, rect.end.y) + + + func _selected_pixels_changed(value : Array) -> void: + for pixel in selected_pixels: + if pixel in Global.current_project.selected_pixels: + Global.current_project.selected_pixels.erase(pixel) + + selected_pixels = value + + for pixel in selected_pixels: + if pixel in Global.current_project.selected_pixels: + continue + else: + Global.current_project.selected_pixels.append(pixel) + + +var polygons := [] # Array of SelectionPolygon(s) +var tween : Tween +var line_offset := Vector2.ZERO setget _offset_changed + + +func _ready() -> void: + tween = Tween.new() + tween.connect("tween_completed", self, "_offset_tween_completed") + add_child(tween) + tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) + tween.start() + + +func _offset_tween_completed(_object, _key) -> void: + self.line_offset = Vector2.ZERO + tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) + tween.start() + + +func _offset_changed(value : Vector2) -> void: + line_offset = value + update() func move_borders_start() -> void: - for shape in get_children(): - shape.temp_polygon = shape.polygon + pass +# for shape in get_children(): +# shape.temp_polygon = shape.polygon func move_borders(move : Vector2) -> void: - for shape in get_children(): - shape._selected_rect.position += move - for i in shape.polygon.size(): - shape.polygon[i] += move + for polygon in polygons: + polygon.rect_outline.position += move + for i in polygon.border.size(): + polygon.border[i] += move func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: @@ -27,17 +82,80 @@ func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: # for i in shape.polygon.size(): # shape.polygon[i] -= diff # Temporarily set the polygon back to be used for undoredo var undo_data = _get_undo_data(false) - for shape in get_children(): - shape.temp_polygon = shape.polygon + for polygon in polygons: +# polygon.temp_polygon = polygon.polygon var diff := new_pos - old_pos - var selected_pixels_copy = shape.local_selected_pixels.duplicate() + var selected_pixels_copy = polygon.selected_pixels.duplicate() for i in selected_pixels_copy.size(): selected_pixels_copy[i] += diff - shape.local_selected_pixels = selected_pixels_copy + polygon.selected_pixels = selected_pixels_copy commit_undo("Rectangle Select", undo_data) +func select_rect(merge := true) -> void: + var project : Project = Global.current_project + var polygon : SelectionPolygon = polygons[-1] + polygon.selected_pixels = [] + project.selections.append(polygon) + var selected_pixels_copy = polygon.selected_pixels.duplicate() + for x in range(polygon.rect_outline.position.x, polygon.rect_outline.end.x): + for y in range(polygon.rect_outline.position.y, polygon.rect_outline.end.y): + var pos := Vector2(x, y) + selected_pixels_copy.append(pos) + + polygon.selected_pixels = selected_pixels_copy + if polygon.selected_pixels.size() == 0: + polygons.erase(polygon) + return + merge_multiple_selections(polygon, merge) + if not merge: + polygon.selected_pixels = [] + polygons.erase(polygon) + + +func merge_multiple_selections(polygon : SelectionPolygon, merge := true) -> void: + if polygons.size() < 2: + return + var to_erase := [] + for p in polygons: + if p == polygon: + continue + if merge: + var arr = Geometry.merge_polygons_2d(polygon.border, p.border) +# print(arr.size()) + if arr.size() == 1: # if the selections intersect + polygon.border = arr[0] + polygon.rect_outline = polygon.rect_outline.merge(p.rect_outline) + var selected_pixels_copy = polygon.selected_pixels.duplicate() + for pixel in p.selected_pixels: + selected_pixels_copy.append(pixel) +# selection.clear_selection_on_tree_exit = false +# selection.queue_free() +# polygons.erase(p) + to_erase.append(p) + polygon.selected_pixels = selected_pixels_copy + else: + var arr = Geometry.clip_polygons_2d(p.border, polygon.border) + if arr.size() == 0: # if the new selection completely overlaps the current + p.selected_pixels = [] + to_erase.append(p) + else: # if the selections intersect + p.border = arr[0] + var selected_pixels_copy = p.selected_pixels.duplicate() + for pixel in polygon.selected_pixels: + selected_pixels_copy.erase(pixel) + p.selected_pixels = selected_pixels_copy + for i in range(1, arr.size()): + var rect = get_bounding_rectangle(arr[i]) + var new_polygon := SelectionPolygon.new(rect) + new_polygon.border = arr[i] + polygons.append(new_polygon) + + for p in to_erase: + polygons.erase(p) + + func move_content_start() -> void: move_borders_start() for shape in get_children(): @@ -70,14 +188,16 @@ func commit_undo(action : String, undo_data : Dictionary) -> void: project.undos += 1 project.undo_redo.create_action(action) + project.undo_redo.add_do_property(project, "selections", redo_data["selections"]) + project.undo_redo.add_undo_property(project, "selections", undo_data["selections"]) var i := 0 - for shape in get_children(): - project.undo_redo.add_do_property(shape, "temp_polygon", redo_data["temp_polygon_%s" % i]) - project.undo_redo.add_do_property(shape, "_selected_rect", redo_data["_selected_rect_%s" % i]) - project.undo_redo.add_do_property(shape, "local_selected_pixels", redo_data["local_selected_pixels_%s" % i]) - project.undo_redo.add_undo_property(shape, "temp_polygon", undo_data["temp_polygon_%s" % i]) - project.undo_redo.add_undo_property(shape, "_selected_rect", undo_data["_selected_rect_%s" % i]) - project.undo_redo.add_undo_property(shape, "local_selected_pixels", undo_data["local_selected_pixels_%s" % i]) + for polygon in polygons: + project.undo_redo.add_do_property(polygon, "border", redo_data["border_%s" % i]) + project.undo_redo.add_do_property(polygon, "rect_outline", redo_data["rect_outline_%s" % i]) + project.undo_redo.add_do_property(polygon, "selected_pixels", redo_data["selected_pixels_%s" % i]) + project.undo_redo.add_undo_property(polygon, "border", undo_data["border_%s" % i]) + project.undo_redo.add_undo_property(polygon, "rect_outline", undo_data["rect_outline_%s" % i]) + project.undo_redo.add_undo_property(polygon, "selected_pixels", undo_data["selected_pixels_%s" % i]) i += 1 if "image_data" in undo_data: @@ -93,10 +213,11 @@ func _get_undo_data(undo_image : bool) -> Dictionary: var data = {} var project := Global.current_project var i := 0 - for shape in get_children(): - data["temp_polygon_%s" % i] = shape.temp_polygon - data["_selected_rect_%s" % i] = shape._selected_rect - data["local_selected_pixels_%s" % i] = shape.local_selected_pixels + data["selections"] = project.selections + for polygon in polygons: + data["border_%s" % i] = polygon.border + data["rect_outline_%s" % i] = polygon.rect_outline + data["selected_pixels_%s" % i] = polygon.selected_pixels i += 1 # data["selected_rect"] = Global.current_project.selected_rect if undo_image: @@ -104,11 +225,130 @@ func _get_undo_data(undo_image : bool) -> Dictionary: image.unlock() data["image_data"] = image.data image.lock() - for d in data.keys(): - print(d, data[d]) +# for d in data.keys(): +# print(d, data[d]) return data +func _draw() -> void: + for polygon in polygons: + var points : PoolVector2Array = polygon.border +# print(polygon) + for i in range(1, points.size() + 1): + var point0 = points[i - 1] + var point1 + if i >= points.size(): + point1 = points[0] + else: + point1 = points[i] + var start_x = min(point0.x, point1.x) + var start_y = min(point0.y, point1.y) + var end_x = max(point0.x, point1.x) + var end_y = max(point0.y, point1.y) + + var start := Vector2(start_x, start_y) + var end := Vector2(end_x, end_y) + draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) + +# if !polygon.selected_pixels: +# return + # draw_polygon(Global.current_project.get_selection_polygon(), [Color(1, 1, 1, 0.5)]) + # var rect_pos := _selected_rect.position + # var rect_end := _selected_rect.end + # draw_circle(rect_pos, 1, Color.gray) + # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) + # draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) + # draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + # draw_circle(rect_end, 1, Color.gray) + # draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) + # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) + # draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) + # draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + +# if !local_image.is_empty(): +# draw_texture(local_image_texture, _selected_rect.position, Color(1, 1, 1, 0.5)) + + # if _move_pixel: + # draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) + + +# Taken and modified from https://github.com/juddrgledhill/godot-dashed-line +func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Color, width := 1.0, dash_length := 1.0, cap_end := false, antialiased := false) -> void: + var length = (to - from).length() + var normal = (to - from).normalized() + var dash_step = normal * dash_length + + var horizontal : bool = from.y == to.y + var _offset : Vector2 + if horizontal: + _offset = Vector2(line_offset.x, 0) + else: + _offset = Vector2(0, line_offset.y) + + if length < dash_length: # not long enough to dash + draw_line(from, to, color, width, antialiased) + return + + else: + var draw_flag = true + var segment_start = from + var steps = length/dash_length + for _start_length in range(0, steps + 1): + var segment_end = segment_start + dash_step + + var start = segment_start + _offset + start.x = min(start.x, to.x) + start.y = min(start.y, to.y) + + var end = segment_end + _offset + end.x = min(end.x, to.x) + end.y = min(end.y, to.y) + if draw_flag: + draw_line(start, end, color, width, antialiased) + else: + draw_line(start, end, color2, width, antialiased) + if _offset.length() < 1: + draw_line(from, from + _offset, color2, width, antialiased) + else: + var from_offseted : Vector2 = from + _offset + var halfway_point : Vector2 = from_offseted + if horizontal: + halfway_point += Vector2.LEFT + else: + halfway_point += Vector2.UP + + from_offseted.x = min(from_offseted.x, to.x) + from_offseted.y = min(from_offseted.y, to.y) + draw_line(halfway_point, from_offseted, color2, width, antialiased) + draw_line(from, halfway_point, color, width, antialiased) + + segment_start = segment_end + draw_flag = !draw_flag + + if cap_end: + draw_line(segment_start, to, color, width, antialiased) + + +func get_bounding_rectangle(polygon : PoolVector2Array) -> Rect2: + var rect := Rect2() + var xmin = polygon[0].x + var xmax = polygon[0].x + var ymin = polygon[0].y + var ymax = polygon[0].y + for edge in polygon: + if edge.x < xmin: + xmin = edge.x + if edge.x > xmax: + xmax = edge.x + if edge.y < ymin: + ymin = edge.y + if edge.y > ymax: + ymax = edge.y + rect.position = Vector2(xmin, ymin) + rect.end = Vector2(xmax, ymax) + return rect + + func generate_rect(pixels : Array) -> Rect2: if !pixels: return Rect2() From d919599b43b3c6565ee23eb34e6ddac120ecda2e Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Wed, 3 Mar 2021 03:48:07 +0200 Subject: [PATCH 13/69] Move tool works again Buggy and messy, of course. --- src/Tools/RectSelect.gd | 3 ++ src/UI/Canvas/Selection.gd | 79 ++++++++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 3dda7fbca6ba..4e257b8a63e4 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -10,12 +10,15 @@ var _move := false func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_end() for selection in Global.canvas.selection.polygons: if selection.rect_outline.has_point(position): current_selection = selection if !current_selection: if !Tools.shift and !Tools.control: + for p in Global.canvas.selection.polygons: + p.selected_pixels = [] Global.canvas.selection.polygons.clear() _start = Rect2(position, Vector2.ZERO) var new_selection = Global.canvas.selection.SelectionPolygon.new(_start) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index ec36da8dcd01..f90eabb56351 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -3,11 +3,12 @@ extends Node2D class SelectionPolygon: # var project : Project - var border := PoolVector2Array() + var border := [] var rect_outline : Rect2 # var rects := [] # Array of Rect2s var selected_pixels := [] setget _selected_pixels_changed # Array of Vector2s - the selected pixels - + var image := Image.new() setget _image_changed + var image_texture := ImageTexture.new() func _init(rect : Rect2) -> void: rect_outline = rect @@ -39,6 +40,12 @@ class SelectionPolygon: Global.current_project.selected_pixels.append(pixel) + func _image_changed(value : Image) -> void: + image = value + image_texture = ImageTexture.new() + image_texture.create_from_image(image, 0) + + var polygons := [] # Array of SelectionPolygon(s) var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed @@ -158,28 +165,28 @@ func merge_multiple_selections(polygon : SelectionPolygon, merge := true) -> voi func move_content_start() -> void: move_borders_start() - for shape in get_children(): - if !shape.local_image.is_empty(): + for p in polygons: + if !p.image.is_empty(): return - shape.local_image = shape.get_image() - shape.local_image_texture.create_from_image(shape.local_image, 0) + p.image = get_image_from_polygon(p) var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - shape._clear_image.resize(shape._selected_rect.size.x, shape._selected_rect.size.y, Image.INTERPOLATE_NEAREST) - cel_image.blit_rect_mask(shape._clear_image, shape.local_image, Rect2(Vector2.ZERO, shape._selected_rect.size), shape._selected_rect.position) +# shape._clear_image.resize(shape._selected_rect.size.x, shape._selected_rect.size.y, Image.INTERPOLATE_NEAREST) + var clear_image := Image.new() + clear_image.create(p.image.get_width(), p.image.get_height(), false, Image.FORMAT_RGBA8) + cel_image.blit_rect_mask(clear_image, p.image, Rect2(Vector2.ZERO, p.rect_outline.size), p.rect_outline.position) Global.canvas.update_texture(project.current_layer) func move_content_end() -> void: - for shape in get_children(): - if shape.local_image.is_empty(): + for p in polygons: + if p.image.is_empty(): return var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(shape.local_image, shape.local_image, Rect2(Vector2.ZERO, shape._selected_rect.size), shape._selected_rect.position) + cel_image.blit_rect_mask(p.image, p.image, Rect2(Vector2.ZERO, p.rect_outline.size), p.rect_outline.position) Global.canvas.update_texture(project.current_layer) - shape.local_image = Image.new() - shape.local_image_texture = ImageTexture.new() + p.image = Image.new() func commit_undo(action : String, undo_data : Dictionary) -> void: @@ -231,8 +238,8 @@ func _get_undo_data(undo_image : bool) -> Dictionary: func _draw() -> void: - for polygon in polygons: - var points : PoolVector2Array = polygon.border + for p in polygons: + var points : Array = p.border # print(polygon) for i in range(1, points.size() + 1): var point0 = points[i - 1] @@ -250,9 +257,11 @@ func _draw() -> void: var end := Vector2(end_x, end_y) draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) + if !p.image.is_empty(): + draw_texture(p.image_texture, p.rect_outline.position, Color(1, 1, 1, 1)) + # if !polygon.selected_pixels: # return - # draw_polygon(Global.current_project.get_selection_polygon(), [Color(1, 1, 1, 0.5)]) # var rect_pos := _selected_rect.position # var rect_end := _selected_rect.end # draw_circle(rect_pos, 1, Color.gray) @@ -265,8 +274,6 @@ func _draw() -> void: # draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) # draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) -# if !local_image.is_empty(): -# draw_texture(local_image_texture, _selected_rect.position, Color(1, 1, 1, 0.5)) # if _move_pixel: # draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) @@ -329,13 +336,13 @@ func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Colo draw_line(segment_start, to, color, width, antialiased) -func get_bounding_rectangle(polygon : PoolVector2Array) -> Rect2: +func get_bounding_rectangle(borders : PoolVector2Array) -> Rect2: var rect := Rect2() - var xmin = polygon[0].x - var xmax = polygon[0].x - var ymin = polygon[0].y - var ymax = polygon[0].y - for edge in polygon: + var xmin = borders[0].x + var xmax = borders[0].x + var ymin = borders[0].y + var ymax = borders[0].y + for edge in borders: if edge.x < xmin: xmin = edge.x if edge.x > xmax: @@ -349,6 +356,30 @@ func get_bounding_rectangle(polygon : PoolVector2Array) -> Rect2: return rect +func get_image_from_polygon(polygon : SelectionPolygon) -> Image: + var rect : Rect2 = polygon.rect_outline + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + var image := Image.new() + image = cel_image.get_rect(rect) + if polygon.border.size() > 4: + image.lock() + var image_pixel := Vector2.ZERO + for x in range(rect.position.x, rect.end.x): + image_pixel.y = 0 + for y in range(rect.position.y, rect.end.y): + var pos := Vector2(x, y) + if not pos in polygon.selected_pixels: + image.set_pixelv(image_pixel, Color(0, 0, 0, 0)) + image_pixel.y += 1 + image_pixel.x += 1 + + image.unlock() +# image.create(_selected_rect.size.x, _selected_rect.size.y, false, Image.FORMAT_RGBA8) + + return image + + func generate_rect(pixels : Array) -> Rect2: if !pixels: return Rect2() From 2df3b62f32cf3fb995edca9b730b125acf983edd Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 5 Mar 2021 19:00:37 +0200 Subject: [PATCH 14/69] Remove unneeded code and restore selection move undoredo logic --- project.godot | 6 - src/Tools/SelectionShape.gd | 370 ---------------------------------- src/Tools/SelectionShape.tscn | 12 -- src/UI/Canvas/Selection.gd | 172 ++++------------ 4 files changed, 34 insertions(+), 526 deletions(-) delete mode 100644 src/Tools/SelectionShape.gd delete mode 100644 src/Tools/SelectionShape.tscn diff --git a/project.godot b/project.godot index 6337185871b4..dbb7251aef41 100644 --- a/project.godot +++ b/project.godot @@ -99,11 +99,6 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/Project.gd" }, { -"base": "Polygon2D", -"class": "SelectionShape", -"language": "GDScript", -"path": "res://src/Tools/SelectionShape.gd" -}, { "base": "Guide", "class": "SymmetryGuide", "language": "GDScript", @@ -128,7 +123,6 @@ _global_script_class_icons={ "PaletteSwatch": "", "Patterns": "", "Project": "", -"SelectionShape": "", "SymmetryGuide": "" } diff --git a/src/Tools/SelectionShape.gd b/src/Tools/SelectionShape.gd deleted file mode 100644 index c0781a50a71c..000000000000 --- a/src/Tools/SelectionShape.gd +++ /dev/null @@ -1,370 +0,0 @@ -class_name SelectionShape extends Polygon2D - - -var line_offset := Vector2.ZERO setget _offset_changed -var tween : Tween -var local_selected_pixels := [] setget _local_selected_pixels_changed # Array of Vector2s -var local_image := Image.new() -var local_image_texture := ImageTexture.new() -var clear_selection_on_tree_exit := true -var temp_polygon : PoolVector2Array setget _temp_polygon_changed -var _selected_rect := Rect2(0, 0, 0, 0) -var _move_image := Image.new() -var _move_texture := ImageTexture.new() -var _clear_image := Image.new() -var _move_pixel := false -var _clipboard := Image.new() -var _undo_data := {} - - -func _ready() -> void: - tween = Tween.new() - tween.connect("tween_completed", self, "_offset_tween_completed") - add_child(tween) - tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) - tween.start() - _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) - _clear_image.fill(Color(0, 0, 0, 0)) - - -func _offset_tween_completed(_object, _key) -> void: - self.line_offset = Vector2.ZERO - tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) - tween.start() - - -func _offset_changed(value : Vector2) -> void: - line_offset = value - update() - - -func _draw() -> void: - var points : PoolVector2Array = polygon - for i in range(1, points.size() + 1): - var point0 = points[i - 1] - var point1 - if i >= points.size(): - point1 = points[0] - else: - point1 = points[i] - var start_x = min(point0.x, point1.x) - var start_y = min(point0.y, point1.y) - var end_x = max(point0.x, point1.x) - var end_y = max(point0.y, point1.y) - - var start := Vector2(start_x, start_y) - var end := Vector2(end_x, end_y) - draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) - - if !local_selected_pixels: - return -# draw_polygon(Global.current_project.get_selection_polygon(), [Color(1, 1, 1, 0.5)]) -# var rect_pos := _selected_rect.position -# var rect_end := _selected_rect.end -# draw_circle(rect_pos, 1, Color.gray) -# draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) -# draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) -# draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) -# draw_circle(rect_end, 1, Color.gray) -# draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) -# draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) -# draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) -# draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) - - if !local_image.is_empty(): - draw_texture(local_image_texture, _selected_rect.position, Color(1, 1, 1, 0.5)) - -# if _move_pixel: -# draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) - - -# Taken and modified from https://github.com/juddrgledhill/godot-dashed-line -func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Color, width := 1.0, dash_length := 1.0, cap_end := false, antialiased := false) -> void: - var length = (to - from).length() - var normal = (to - from).normalized() - var dash_step = normal * dash_length - - var horizontal : bool = from.y == to.y - var _offset : Vector2 - if horizontal: - _offset = Vector2(line_offset.x, 0) - else: - _offset = Vector2(0, line_offset.y) - - if length < dash_length: # not long enough to dash - draw_line(from, to, color, width, antialiased) - return - - else: - var draw_flag = true - var segment_start = from - var steps = length/dash_length - for _start_length in range(0, steps + 1): - var segment_end = segment_start + dash_step - - var start = segment_start + _offset - start.x = min(start.x, to.x) - start.y = min(start.y, to.y) - - var end = segment_end + _offset - end.x = min(end.x, to.x) - end.y = min(end.y, to.y) - if draw_flag: - draw_line(start, end, color, width, antialiased) - else: - draw_line(start, end, color2, width, antialiased) - if _offset.length() < 1: - draw_line(from, from + _offset, color2, width, antialiased) - else: - var from_offseted : Vector2 = from + _offset - var halfway_point : Vector2 = from_offseted - if horizontal: - halfway_point += Vector2.LEFT - else: - halfway_point += Vector2.UP - - from_offseted.x = min(from_offseted.x, to.x) - from_offseted.y = min(from_offseted.y, to.y) - draw_line(halfway_point, from_offseted, color2, width, antialiased) - draw_line(from, halfway_point, color, width, antialiased) - - segment_start = segment_end - draw_flag = !draw_flag - - if cap_end: - draw_line(segment_start, to, color, width, antialiased) - - -func _local_selected_pixels_changed(value : Array) -> void: - for pixel in local_selected_pixels: - if pixel in Global.current_project.selected_pixels: - Global.current_project.selected_pixels.erase(pixel) - - local_selected_pixels = value - - for pixel in local_selected_pixels: - if pixel in Global.current_project.selected_pixels: - continue - else: - Global.current_project.selected_pixels.append(pixel) - -# if value: -# Global.canvas.get_node("Selection").generate_polygons() - - -func _temp_polygon_changed(value : PoolVector2Array) -> void: - temp_polygon = value - polygon = temp_polygon - - -func has_point(position : Vector2) -> bool: - return Geometry.is_point_in_polygon(position, polygon) - - -func get_rect() -> Rect2: - return _selected_rect - - -func set_rect(rect : Rect2) -> void: - _selected_rect = rect - polygon[0] = rect.position - polygon[1] = Vector2(rect.end.x, rect.position.y) - polygon[2] = rect.end - polygon[3] = Vector2(rect.position.x, rect.end.y) -# visible = not rect.has_no_area() - - -func select_rect(merge := true) -> void: - var project : Project = Global.current_project - self.local_selected_pixels = [] - var selected_pixels_copy = local_selected_pixels.duplicate() - for x in range(_selected_rect.position.x, _selected_rect.end.x): - for y in range(_selected_rect.position.y, _selected_rect.end.y): - var pos := Vector2(x, y) -# if polygon.size() > 4: # if it's not a rectangle -# if !Geometry.is_point_in_polygon(pos, polygon): -# continue -# if x < 0 or x >= project.size.x: -# continue -# if y < 0 or y >= project.size.y: -# continue - selected_pixels_copy.append(pos) - - self.local_selected_pixels = selected_pixels_copy - if local_selected_pixels.size() == 0: - queue_free() - return - merge_multiple_selections(merge) - if not merge: - queue_free() -# var undo_data = _get_undo_data(false) -# Global.current_project.selected_rect = _selected_rect -# commit_undo("Rectangle Select", undo_data) - - -func merge_multiple_selections(merge := true) -> void: - if Global.current_project.selections.size() < 2: - return - for selection in Global.current_project.selections: - if selection == self: - continue - if merge: - var arr = Geometry.merge_polygons_2d(polygon, selection.polygon) -# print(arr.size()) - if arr.size() == 1: # if the selections intersect - set_polygon(arr[0]) - _selected_rect = _selected_rect.merge(selection._selected_rect) - var selected_pixels_copy = local_selected_pixels.duplicate() - for pixel in selection.local_selected_pixels: - selected_pixels_copy.append(pixel) - selection.clear_selection_on_tree_exit = false - selection.queue_free() - self.local_selected_pixels = selected_pixels_copy - else: - var arr = Geometry.clip_polygons_2d(selection.polygon, polygon) - if arr.size() == 0: # if the new selection completely overlaps the current - selection.queue_free() - else: # if the selections intersect - selection.set_polygon(arr[0]) - var selected_pixels_copy = selection.local_selected_pixels.duplicate() - for pixel in local_selected_pixels: - selected_pixels_copy.erase(pixel) - selection.local_selected_pixels = selected_pixels_copy - for i in range(1, arr.size()): - var selection_shape = load("res://src/Tools/SelectionShape.tscn").instance() - Global.current_project.selections.append(selection_shape) - Global.canvas.selection.add_child(selection_shape) - selection_shape.set_polygon(arr[i]) - - -func get_image() -> Image: - var project : Project = Global.current_project - var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var image := Image.new() - image = cel_image.get_rect(_selected_rect) -# print(polygon.size()) - if polygon.size() > 4: - image.lock() - var image_pixel := Vector2.ZERO - for x in range(_selected_rect.position.x, _selected_rect.end.x): - image_pixel.y = 0 - for y in range(_selected_rect.position.y, _selected_rect.end.y): - var pos := Vector2(x, y) -# if not Geometry.is_point_in_polygon(pos, polygon): - if not pos in local_selected_pixels: - # print(pixel) - image.set_pixelv(image_pixel, Color(0, 0, 0, 0)) - image_pixel.y += 1 - image_pixel.x += 1 - - image.unlock() -# image.create(_selected_rect.size.x, _selected_rect.size.y, false, Image.FORMAT_RGBA8) - - return image - - -#func move_start(move_pixel : bool) -> void: -# if not move_pixel: -# return -# -# _undo_data = _get_undo_data(true) -# var project := Global.current_project -# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image -# -# var rect = Rect2(Vector2.ZERO, project.size) -# _clipped_rect = rect.clip(_selected_rect) -# _move_image = image.get_rect(_clipped_rect) -# _move_texture.create_from_image(_move_image, 0) -# -# var size := _clipped_rect.size -# rect = Rect2(Vector2.ZERO, size) -# _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) -# image.blit_rect(_clear_image, rect, _clipped_rect.position) -# Global.canvas.update_texture(project.current_layer) -# -# _move_pixel = true -# update() -# -# -#func move_end() -> void: -# var undo_data = _undo_data if _move_pixel else _get_undo_data(false) -# -# if _move_pixel: -# var project := Global.current_project -# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image -# var size := _clipped_rect.size -# var rect = Rect2(Vector2.ZERO, size) -# image.blit_rect_mask(_move_image, _move_image, rect, _clipped_rect.position) -# _move_pixel = false -# update() -# -# Global.current_project.selected_rect = _selected_rect -# commit_undo("Rectangle Select", undo_data) -# _undo_data.clear() - - -func copy() -> void: - if _selected_rect.has_no_area(): - return - - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - _clipboard = image.get_rect(_selected_rect) - if _clipboard.is_invisible(): - return - var brush = _clipboard.get_rect(_clipboard.get_used_rect()) - project.brushes.append(brush) - Brushes.add_project_brush(brush) - -#func cut() -> void: # This is basically the same as copy + delete -# if _selected_rect.has_no_area(): -# return -# -# var undo_data = _get_undo_data(true) -# var project := Global.current_project -# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image -# var size := _selected_rect.size -# var rect = Rect2(Vector2.ZERO, size) -# _clipboard = image.get_rect(_selected_rect) -# if _clipboard.is_invisible(): -# return -# -# _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) -# var brush = _clipboard.get_rect(_clipboard.get_used_rect()) -# project.brushes.append(brush) -# Brushes.add_project_brush(brush) -## move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning -# image.blit_rect(_clear_image, rect, _selected_rect.position) -# commit_undo("Draw", undo_data) -# -#func paste() -> void: -# if _clipboard.get_size() <= Vector2.ZERO: -# return -# -# var undo_data = _get_undo_data(true) -# var project := Global.current_project -# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image -# var size := _selected_rect.size -# var rect = Rect2(Vector2.ZERO, size) -# image.blend_rect(_clipboard, rect, _selected_rect.position) -## move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning -# commit_undo("Draw", undo_data) -# -# -#func delete() -> void: -# var undo_data = _get_undo_data(true) -# var project := Global.current_project -# var image : Image = project.frames[project.current_frame].cels[project.current_layer].image -# var size := _selected_rect.size -# var rect = Rect2(Vector2.ZERO, size) -# _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) -# image.blit_rect(_clear_image, rect, _selected_rect.position) -## move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning -# commit_undo("Draw", undo_data) - - -func _on_SelectionShape_tree_exiting() -> void: - get_parent().move_content_end() - Global.current_project.selections.erase(self) - if clear_selection_on_tree_exit: - self.local_selected_pixels = [] diff --git a/src/Tools/SelectionShape.tscn b/src/Tools/SelectionShape.tscn deleted file mode 100644 index 20417e45e4af..000000000000 --- a/src/Tools/SelectionShape.tscn +++ /dev/null @@ -1,12 +0,0 @@ -[gd_scene load_steps=2 format=2] - -[ext_resource path="res://src/Tools/SelectionShape.gd" type="Script" id=1] - -[node name="SelectionShape" type="Polygon2D"] -z_index = 1 -color = Color( 1, 1, 1, 0 ) -invert_enable = true -invert_border = 0.5 -polygon = PoolVector2Array( 0, 0, 0, 0, 0, 0, 0, 0 ) -script = ExtResource( 1 ) -[connection signal="tree_exiting" from="." to="." method="_on_SelectionShape_tree_exiting"] diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index f90eabb56351..6b0bfec55a27 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -49,6 +49,7 @@ class SelectionPolygon: var polygons := [] # Array of SelectionPolygon(s) var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed +var undo_data : Dictionary func _ready() -> void: @@ -71,26 +72,21 @@ func _offset_changed(value : Vector2) -> void: func move_borders_start() -> void: - pass -# for shape in get_children(): -# shape.temp_polygon = shape.polygon + undo_data = _get_undo_data(false) func move_borders(move : Vector2) -> void: for polygon in polygons: polygon.rect_outline.position += move - for i in polygon.border.size(): - polygon.border[i] += move + var borders_copy = polygon.border.duplicate() + for i in borders_copy.size(): + borders_copy[i] += move + + polygon.border = borders_copy func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: -# for shape in get_children(): -# var diff := new_pos - old_pos -# for i in shape.polygon.size(): -# shape.polygon[i] -= diff # Temporarily set the polygon back to be used for undoredo - var undo_data = _get_undo_data(false) for polygon in polygons: -# polygon.temp_polygon = polygon.polygon var diff := new_pos - old_pos var selected_pixels_copy = polygon.selected_pixels.duplicate() for i in selected_pixels_copy.size(): @@ -164,7 +160,7 @@ func merge_multiple_selections(polygon : SelectionPolygon, merge := true) -> voi func move_content_start() -> void: - move_borders_start() +# move_borders_start() for p in polygons: if !p.image.is_empty(): return @@ -189,35 +185,38 @@ func move_content_end() -> void: p.image = Image.new() -func commit_undo(action : String, undo_data : Dictionary) -> void: - var redo_data = _get_undo_data("image_data" in undo_data) +func commit_undo(action : String, _undo_data : Dictionary) -> void: + var redo_data = _get_undo_data("image_data" in _undo_data) var project := Global.current_project project.undos += 1 project.undo_redo.create_action(action) project.undo_redo.add_do_property(project, "selections", redo_data["selections"]) - project.undo_redo.add_undo_property(project, "selections", undo_data["selections"]) + project.undo_redo.add_undo_property(project, "selections", _undo_data["selections"]) var i := 0 for polygon in polygons: project.undo_redo.add_do_property(polygon, "border", redo_data["border_%s" % i]) + print(redo_data["border_%s" % i], " Undo ", _undo_data["border_%s" % i]) project.undo_redo.add_do_property(polygon, "rect_outline", redo_data["rect_outline_%s" % i]) project.undo_redo.add_do_property(polygon, "selected_pixels", redo_data["selected_pixels_%s" % i]) - project.undo_redo.add_undo_property(polygon, "border", undo_data["border_%s" % i]) - project.undo_redo.add_undo_property(polygon, "rect_outline", undo_data["rect_outline_%s" % i]) - project.undo_redo.add_undo_property(polygon, "selected_pixels", undo_data["selected_pixels_%s" % i]) + project.undo_redo.add_undo_property(polygon, "border", _undo_data["border_%s" % i]) + project.undo_redo.add_undo_property(polygon, "rect_outline", _undo_data["rect_outline_%s" % i]) + project.undo_redo.add_undo_property(polygon, "selected_pixels", _undo_data["selected_pixels_%s" % i]) i += 1 - if "image_data" in undo_data: + if "image_data" in _undo_data: var image : Image = project.frames[project.current_frame].cels[project.current_layer].image project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) - project.undo_redo.add_undo_property(image, "data", undo_data["image_data"]) + project.undo_redo.add_undo_property(image, "data", _undo_data["image_data"]) project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) project.undo_redo.commit_action() + undo_data.clear() + func _get_undo_data(undo_image : bool) -> Dictionary: - var data = {} + var data := {} var project := Global.current_project var i := 0 data["selections"] = project.selections @@ -226,7 +225,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: data["rect_outline_%s" % i] = polygon.rect_outline data["selected_pixels_%s" % i] = polygon.selected_pixels i += 1 -# data["selected_rect"] = Global.current_project.selected_rect + if undo_image: var image : Image = project.frames[project.current_frame].cels[project.current_layer].image image.unlock() @@ -240,7 +239,6 @@ func _get_undo_data(undo_image : bool) -> Dictionary: func _draw() -> void: for p in polygons: var points : Array = p.border -# print(polygon) for i in range(1, points.size() + 1): var point0 = points[i - 1] var point1 @@ -262,17 +260,18 @@ func _draw() -> void: # if !polygon.selected_pixels: # return - # var rect_pos := _selected_rect.position - # var rect_end := _selected_rect.end - # draw_circle(rect_pos, 1, Color.gray) - # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), 1, Color.gray) - # draw_circle(Vector2(rect_end.x, rect_pos.y), 1, Color.gray) - # draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) - # draw_circle(rect_end, 1, Color.gray) - # draw_circle(Vector2(rect_end.x, rect_end.y), 1, Color.gray) - # draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), 1, Color.gray) - # draw_circle(Vector2(rect_pos.x, rect_end.y), 1, Color.gray) - # draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), 1, Color.gray) + var rect_pos : Vector2 = p.rect_outline.position + var rect_end : Vector2 = p.rect_outline.end + var radius := 0.5 + draw_circle(rect_pos, radius, Color.gray) + draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), radius, Color.gray) + draw_circle(Vector2(rect_end.x, rect_pos.y), radius, Color.gray) + draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), radius, Color.gray) + draw_circle(rect_end, radius, Color.gray) + draw_circle(Vector2(rect_end.x, rect_end.y), radius, Color.gray) + draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), radius, Color.gray) + draw_circle(Vector2(rect_pos.x, rect_end.y), radius, Color.gray) + draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), radius, Color.gray) # if _move_pixel: @@ -336,7 +335,7 @@ func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Colo draw_line(segment_start, to, color, width, antialiased) -func get_bounding_rectangle(borders : PoolVector2Array) -> Rect2: +func get_bounding_rectangle(borders : Array) -> Rect2: var rect := Rect2() var xmin = borders[0].x var xmax = borders[0].x @@ -378,106 +377,3 @@ func get_image_from_polygon(polygon : SelectionPolygon) -> Image: # image.create(_selected_rect.size.x, _selected_rect.size.y, false, Image.FORMAT_RGBA8) return image - - -func generate_rect(pixels : Array) -> Rect2: - if !pixels: - return Rect2() - var rect := Rect2() - rect.position = pixels[0] - var reached_bottom := false - var bottom_right_corner - var p = pixels[0] - while p + Vector2.DOWN in pixels: - p += Vector2.DOWN -# while p + Vector2.RIGHT in pixels: -# p += Vector2.RIGHT - bottom_right_corner = p -# for p in pixels: -# if p + Vector2.DOWN in pixels: -# continue -# reached_bottom = true -# if p + Vector2.RIGHT in pixels: -# continue -# if reached_bottom and !bottom_right_corner: -# bottom_right_corner = p - rect.end = bottom_right_corner + Vector2.ONE - return rect - - -func get_rect_border(rect : Rect2) -> PoolVector2Array: - var border := PoolVector2Array() - border.append(rect.position) - border.append(Vector2(rect.end.x, rect.position.y)) - border.append(rect.end) - border.append(Vector2(rect.position.x, rect.end.y)) - return border - - -#func generate_polygons(): -## selection_polygons.clear() -## selection_polygons.append(SelectionPolygon.new()) -## for pixel in Global.current_project.selected_pixels: -## var current_polygon : SelectionPolygon = selection_polygons[0] -## var rect = Rect2(pixel, Vector2.ONE) -## var pixel_border = get_rect_border(rect) -## var arr = Geometry.merge_polygons_2d(pixel_border, current_polygon.border) -### print("Arr ", arr) -## if arr.size() == 1: # if the selections intersect -### current_polygon.rects.append(rect) -## current_polygon.rect_outline.merge(rect) -## current_polygon.border = arr[0] -## -## -## return -# var selected_pixels_copy := Global.current_project.selected_pixels.duplicate() -# var rects := [] -# while selected_pixels_copy.size() > 0: -# var rect : Rect2 = generate_rect(selected_pixels_copy) -# print("!!!") -# if !rect: -# break -# for pixel in Global.current_project.selected_pixels: -# if rect.has_point(pixel): -## print("POINT") -# selected_pixels_copy.erase(pixel) -# -# rects.append(rect) -# -# print(rects) -## print("e ", selected_pixels_copy) -# -# if !rects: -# return -## var polygons := [SelectionPolygon.new()] -# selection_polygons.clear() -# var polygons := [SelectionPolygon.new()] -# var curr_polyg := 0 -# -# if rects.size() == 1: -# polygons[0].rects.append(rects[0]) -# polygons[0].rect_outline = rects[0] -# polygons[0].selected_pixels = Global.current_project.selected_pixels.duplicate() -# var border : PoolVector2Array = get_rect_border(rects[0]) -# polygons[0].border = border -# selection_polygons = polygons -# return -# -# for i in rects.size(): -# var current_polygon : SelectionPolygon = polygons[curr_polyg] -# var rect : Rect2 = rects[i] -# var outlines : PoolVector2Array = get_rect_border(rect) -# -## var rect_prev : Rect2 = rects[i - 1] -## var outlines_prev : PoolVector2Array = get_rect_border(rect_prev) -# -# var arr = Geometry.merge_polygons_2d(outlines, current_polygon.border) -# print("Arr ", arr) -# if arr.size() == 1: # if the selections intersect -# current_polygon.rects.append(rect) -## if not rect_prev in current_polygon.rects: -## current_polygon.rects.append(rect_prev) -# current_polygon.rect_outline.merge(rect) -# current_polygon.border = arr[0] -# -# selection_polygons = polygons From d7e4c55c8074e829b3b29ebe01583463d097b6c2 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 6 Mar 2021 03:49:59 +0200 Subject: [PATCH 15/69] Made Selection.gd have one big preview_image for when moving content, instead of each polygon having its own image Could be further optimized for some specific cases. We could also remove selected_pixels from SelectionPolygon. --- src/Tools/Move.gd | 3 +- src/UI/Canvas/Selection.gd | 87 ++++++++++++++++---------------------- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index d1b33e929ade..4d83122c53b2 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -14,9 +14,8 @@ func draw_start(position : Vector2) -> void: func draw_move(position : Vector2) -> void: if Global.current_project.selected_pixels: - Global.canvas.selection.move_borders(position - offset) + Global.canvas.selection.move_content(position - offset) offset = position -# Global.selection_rectangl.move_rect(position - offset) else: Global.canvas.move_preview_location = position - starting_pos offset = position diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 6b0bfec55a27..547a3ca4420b 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -7,8 +7,6 @@ class SelectionPolygon: var rect_outline : Rect2 # var rects := [] # Array of Rect2s var selected_pixels := [] setget _selected_pixels_changed # Array of Vector2s - the selected pixels - var image := Image.new() setget _image_changed - var image_texture := ImageTexture.new() func _init(rect : Rect2) -> void: rect_outline = rect @@ -40,15 +38,12 @@ class SelectionPolygon: Global.current_project.selected_pixels.append(pixel) - func _image_changed(value : Image) -> void: - image = value - image_texture = ImageTexture.new() - image_texture.create_from_image(image, 0) - - var polygons := [] # Array of SelectionPolygon(s) var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed +var move_preview_location := Vector2.ZERO +var preview_image := Image.new() +var preview_image_texture : ImageTexture var undo_data : Dictionary @@ -161,28 +156,23 @@ func merge_multiple_selections(polygon : SelectionPolygon, merge := true) -> voi func move_content_start() -> void: # move_borders_start() - for p in polygons: - if !p.image.is_empty(): - return - p.image = get_image_from_polygon(p) - var project : Project = Global.current_project - var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image -# shape._clear_image.resize(shape._selected_rect.size.x, shape._selected_rect.size.y, Image.INTERPOLATE_NEAREST) - var clear_image := Image.new() - clear_image.create(p.image.get_width(), p.image.get_height(), false, Image.FORMAT_RGBA8) - cel_image.blit_rect_mask(clear_image, p.image, Rect2(Vector2.ZERO, p.rect_outline.size), p.rect_outline.position) - Global.canvas.update_texture(project.current_layer) + get_preview_image() + + +func move_content(move : Vector2) -> void: + move_borders(move) + move_preview_location += move func move_content_end() -> void: - for p in polygons: - if p.image.is_empty(): - return - var project : Project = Global.current_project - var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(p.image, p.image, Rect2(Vector2.ZERO, p.rect_outline.size), p.rect_outline.position) - Global.canvas.update_texture(project.current_layer) - p.image = Image.new() + if preview_image.is_empty(): + return + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) + Global.canvas.update_texture(project.current_layer) + preview_image = Image.new() + move_preview_location = Vector2.ZERO func commit_undo(action : String, _undo_data : Dictionary) -> void: @@ -196,7 +186,6 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: var i := 0 for polygon in polygons: project.undo_redo.add_do_property(polygon, "border", redo_data["border_%s" % i]) - print(redo_data["border_%s" % i], " Undo ", _undo_data["border_%s" % i]) project.undo_redo.add_do_property(polygon, "rect_outline", redo_data["rect_outline_%s" % i]) project.undo_redo.add_do_property(polygon, "selected_pixels", redo_data["selected_pixels_%s" % i]) project.undo_redo.add_undo_property(polygon, "border", _undo_data["border_%s" % i]) @@ -274,8 +263,8 @@ func _draw() -> void: draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), radius, Color.gray) - # if _move_pixel: - # draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) + if !preview_image.is_empty(): + draw_texture(preview_image_texture, move_preview_location, Color(1, 1, 1, 0.5)) # Taken and modified from https://github.com/juddrgledhill/godot-dashed-line @@ -355,25 +344,23 @@ func get_bounding_rectangle(borders : Array) -> Rect2: return rect -func get_image_from_polygon(polygon : SelectionPolygon) -> Image: - var rect : Rect2 = polygon.rect_outline +func get_preview_image() -> void: + if !preview_image.is_empty(): + return var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var image := Image.new() - image = cel_image.get_rect(rect) - if polygon.border.size() > 4: - image.lock() - var image_pixel := Vector2.ZERO - for x in range(rect.position.x, rect.end.x): - image_pixel.y = 0 - for y in range(rect.position.y, rect.end.y): - var pos := Vector2(x, y) - if not pos in polygon.selected_pixels: - image.set_pixelv(image_pixel, Color(0, 0, 0, 0)) - image_pixel.y += 1 - image_pixel.x += 1 - - image.unlock() -# image.create(_selected_rect.size.x, _selected_rect.size.y, false, Image.FORMAT_RGBA8) - - return image + preview_image.copy_from(cel_image) + preview_image.lock() + for x in range(0, project.size.x): + for y in range(0, project.size.y): + var pos := Vector2(x, y) + if not pos in project.selected_pixels: + preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) + preview_image.unlock() + preview_image_texture = ImageTexture.new() + preview_image_texture.create_from_image(preview_image, 0) + + var clear_image := Image.new() + clear_image.create(preview_image.get_width(), preview_image.get_height(), false, Image.FORMAT_RGBA8) + cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), Vector2.ZERO) + Global.canvas.update_texture(project.current_layer) From 468bd6530a7890d4c1dda18402bcc43ece656dd0 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:55:14 +0200 Subject: [PATCH 16/69] UndoRedo support for creating, deleting, merging and clipping selections UndoRedo support for moving content not added in this commit. Should work but needs more testing. This PR also removes selected_pixels from the SelectionPolygon class. --- src/Tools/RectSelect.gd | 16 ++-- src/UI/Canvas/Selection.gd | 147 ++++++++++++++++--------------------- 2 files changed, 76 insertions(+), 87 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 4e257b8a63e4..71f8f95fe855 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -7,23 +7,28 @@ var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO var _drag := false var _move := false +var undo_data : Dictionary func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_end() - for selection in Global.canvas.selection.polygons: + undo_data = Global.canvas.selection._get_undo_data(false) + for selection in Global.current_project.selections: if selection.rect_outline.has_point(position): current_selection = selection if !current_selection: + var selections : Array = Global.current_project.selections.duplicate() if !Tools.shift and !Tools.control: - for p in Global.canvas.selection.polygons: - p.selected_pixels = [] - Global.canvas.selection.polygons.clear() + var selected_pixels : Array = Global.current_project.selected_pixels.duplicate() + selected_pixels.clear() + Global.current_project.selected_pixels = selected_pixels + selections.clear() _start = Rect2(position, Vector2.ZERO) var new_selection = Global.canvas.selection.SelectionPolygon.new(_start) - Global.canvas.selection.polygons.append(new_selection) + selections.append(new_selection) current_selection = new_selection + Global.current_project.selections = selections # for selection in Global.current_project.selections: # selection.queue_free() # current_selection_id = 0 @@ -69,6 +74,7 @@ func draw_end(position : Vector2) -> void: Global.canvas.selection.move_borders_end(position, start_position) else: Global.canvas.selection.select_rect(!Tools.control) + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) # var undo_data = Global.canvas.selection._get_undo_data(false) # current_selection.select_rect(!Tools.control) # Global.canvas.selection.commit_undo("Rectangle Select", undo_data) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 547a3ca4420b..637b23e1ecf5 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -2,11 +2,9 @@ extends Node2D class SelectionPolygon: -# var project : Project var border := [] var rect_outline : Rect2 # var rects := [] # Array of Rect2s - var selected_pixels := [] setget _selected_pixels_changed # Array of Vector2s - the selected pixels func _init(rect : Rect2) -> void: rect_outline = rect @@ -23,22 +21,6 @@ class SelectionPolygon: border[2] = rect.end border[3] = Vector2(rect.position.x, rect.end.y) - - func _selected_pixels_changed(value : Array) -> void: - for pixel in selected_pixels: - if pixel in Global.current_project.selected_pixels: - Global.current_project.selected_pixels.erase(pixel) - - selected_pixels = value - - for pixel in selected_pixels: - if pixel in Global.current_project.selected_pixels: - continue - else: - Global.current_project.selected_pixels.append(pixel) - - -var polygons := [] # Array of SelectionPolygon(s) var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed var move_preview_location := Vector2.ZERO @@ -71,7 +53,7 @@ func move_borders_start() -> void: func move_borders(move : Vector2) -> void: - for polygon in polygons: + for polygon in Global.current_project.selections: polygon.rect_outline.position += move var borders_copy = polygon.border.duplicate() for i in borders_copy.size(): @@ -81,77 +63,80 @@ func move_borders(move : Vector2) -> void: func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: - for polygon in polygons: - var diff := new_pos - old_pos - var selected_pixels_copy = polygon.selected_pixels.duplicate() - for i in selected_pixels_copy.size(): - selected_pixels_copy[i] += diff + var diff := new_pos - old_pos + var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() + for i in selected_pixels_copy.size(): + selected_pixels_copy[i] += diff - polygon.selected_pixels = selected_pixels_copy + Global.current_project.selected_pixels = selected_pixels_copy commit_undo("Rectangle Select", undo_data) func select_rect(merge := true) -> void: var project : Project = Global.current_project - var polygon : SelectionPolygon = polygons[-1] - polygon.selected_pixels = [] - project.selections.append(polygon) - var selected_pixels_copy = polygon.selected_pixels.duplicate() + var polygon : SelectionPolygon = Global.current_project.selections[-1] + var selected_pixels_copy = project.selected_pixels.duplicate() + var polygon_pixels := [] for x in range(polygon.rect_outline.position.x, polygon.rect_outline.end.x): for y in range(polygon.rect_outline.position.y, polygon.rect_outline.end.y): var pos := Vector2(x, y) + polygon_pixels.append(pos) + if pos in selected_pixels_copy or !merge: + continue selected_pixels_copy.append(pos) - polygon.selected_pixels = selected_pixels_copy - if polygon.selected_pixels.size() == 0: - polygons.erase(polygon) + project.selected_pixels = selected_pixels_copy + if polygon_pixels.size() == 0: + project.selections.erase(polygon) return - merge_multiple_selections(polygon, merge) - if not merge: - polygon.selected_pixels = [] - polygons.erase(polygon) + if merge: + merge_selections(polygon) + else: + clip_selections(polygon, polygon_pixels) + project.selections.erase(polygon) -func merge_multiple_selections(polygon : SelectionPolygon, merge := true) -> void: - if polygons.size() < 2: +func merge_selections(polygon : SelectionPolygon) -> void: + if Global.current_project.selections.size() < 2: return var to_erase := [] - for p in polygons: + for p in Global.current_project.selections: if p == polygon: continue - if merge: - var arr = Geometry.merge_polygons_2d(polygon.border, p.border) -# print(arr.size()) - if arr.size() == 1: # if the selections intersect - polygon.border = arr[0] - polygon.rect_outline = polygon.rect_outline.merge(p.rect_outline) - var selected_pixels_copy = polygon.selected_pixels.duplicate() - for pixel in p.selected_pixels: - selected_pixels_copy.append(pixel) -# selection.clear_selection_on_tree_exit = false -# selection.queue_free() -# polygons.erase(p) - to_erase.append(p) - polygon.selected_pixels = selected_pixels_copy + var arr := Geometry.merge_polygons_2d(polygon.border, p.border) + if arr.size() == 1: # if the selections intersect + polygon.border = arr[0] + polygon.rect_outline = polygon.rect_outline.merge(p.rect_outline) + to_erase.append(p) + + for p in to_erase: + Global.current_project.selections.erase(p) + + +func clip_selections(polygon : SelectionPolygon, polygon_pixels : Array) -> void: + var to_erase := [] + for p in Global.current_project.selections: + if p == polygon: + continue + var arr := Geometry.clip_polygons_2d(p.border, polygon.border) + if arr.size() == 0: # if the new selection completely overlaps the current + to_erase.append(p) else: - var arr = Geometry.clip_polygons_2d(p.border, polygon.border) - if arr.size() == 0: # if the new selection completely overlaps the current - p.selected_pixels = [] - to_erase.append(p) - else: # if the selections intersect - p.border = arr[0] - var selected_pixels_copy = p.selected_pixels.duplicate() - for pixel in polygon.selected_pixels: - selected_pixels_copy.erase(pixel) - p.selected_pixels = selected_pixels_copy - for i in range(1, arr.size()): - var rect = get_bounding_rectangle(arr[i]) - var new_polygon := SelectionPolygon.new(rect) - new_polygon.border = arr[i] - polygons.append(new_polygon) + p.border = arr[0] + p.rect_outline = get_bounding_rectangle(p.border) + for i in range(1, arr.size()): + var rect = get_bounding_rectangle(arr[i]) + var new_polygon := SelectionPolygon.new(rect) + new_polygon.border = arr[i] + Global.current_project.selections.append(new_polygon) + + var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() + for pixel in polygon_pixels: + selected_pixels_copy.erase(pixel) + Global.current_project.selected_pixels = selected_pixels_copy for p in to_erase: - polygons.erase(p) + Global.current_project.selections.erase(p) func move_content_start() -> void: @@ -182,15 +167,16 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: project.undos += 1 project.undo_redo.create_action(action) project.undo_redo.add_do_property(project, "selections", redo_data["selections"]) + project.undo_redo.add_do_property(project, "selected_pixels", redo_data["selected_pixels"]) project.undo_redo.add_undo_property(project, "selections", _undo_data["selections"]) + project.undo_redo.add_undo_property(project, "selected_pixels", _undo_data["selected_pixels"]) var i := 0 - for polygon in polygons: - project.undo_redo.add_do_property(polygon, "border", redo_data["border_%s" % i]) - project.undo_redo.add_do_property(polygon, "rect_outline", redo_data["rect_outline_%s" % i]) - project.undo_redo.add_do_property(polygon, "selected_pixels", redo_data["selected_pixels_%s" % i]) - project.undo_redo.add_undo_property(polygon, "border", _undo_data["border_%s" % i]) - project.undo_redo.add_undo_property(polygon, "rect_outline", _undo_data["rect_outline_%s" % i]) - project.undo_redo.add_undo_property(polygon, "selected_pixels", _undo_data["selected_pixels_%s" % i]) + for polygon in Global.current_project.selections: + if "border_%s" % i in _undo_data: + project.undo_redo.add_do_property(polygon, "border", redo_data["border_%s" % i]) + project.undo_redo.add_do_property(polygon, "rect_outline", redo_data["rect_outline_%s" % i]) + project.undo_redo.add_undo_property(polygon, "border", _undo_data["border_%s" % i]) + project.undo_redo.add_undo_property(polygon, "rect_outline", _undo_data["rect_outline_%s" % i]) i += 1 if "image_data" in _undo_data: @@ -209,10 +195,10 @@ func _get_undo_data(undo_image : bool) -> Dictionary: var project := Global.current_project var i := 0 data["selections"] = project.selections - for polygon in polygons: + data["selected_pixels"] = project.selected_pixels + for polygon in Global.current_project.selections: data["border_%s" % i] = polygon.border data["rect_outline_%s" % i] = polygon.rect_outline - data["selected_pixels_%s" % i] = polygon.selected_pixels i += 1 if undo_image: @@ -226,7 +212,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: func _draw() -> void: - for p in polygons: + for p in Global.current_project.selections: var points : Array = p.border for i in range(1, points.size() + 1): var point0 = points[i - 1] @@ -244,9 +230,6 @@ func _draw() -> void: var end := Vector2(end_x, end_y) draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) - if !p.image.is_empty(): - draw_texture(p.image_texture, p.rect_outline.position, Color(1, 1, 1, 1)) - # if !polygon.selected_pixels: # return var rect_pos : Vector2 = p.rect_outline.position From e9f62cb97a2b530d9b3adb20bb06584bb6ff43c7 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 13 Mar 2021 00:52:33 +0200 Subject: [PATCH 17/69] Confirm & cancel selection movement, should support undoredo properly too Press Enter or do any editing to confirm movement, Escape to cancel. I will most likely add UI buttons for confirm and cancel too. --- src/Autoload/DrawingAlgos.gd | 2 + src/Autoload/Global.gd | 4 +- src/Classes/ImageEffect.gd | 1 + src/Classes/Project.gd | 13 ++++ src/Main.gd | 19 +++--- src/Tools/Bucket.gd | 1 + src/Tools/Eraser.gd | 1 + src/Tools/LightenDarken.gd | 1 + src/Tools/Pencil.gd | 1 + src/Tools/RectSelect.gd | 2 +- src/UI/Canvas/Selection.gd | 74 ++++++++++++++++----- src/UI/Dialogs/ExportDialog.gd | 1 + src/UI/Dialogs/ImageEffects/ResizeCanvas.gd | 1 + src/UI/Dialogs/ImageEffects/ScaleImage.gd | 1 + src/UI/TopMenuContainer.gd | 6 +- 15 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 4a8ace06625e..d85c610d2654 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -256,6 +256,7 @@ func scale_image(width : int, height : int, interpolation : int) -> void: func centralize() -> void: + Global.canvas.selection.move_content_confirm() # 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: @@ -277,6 +278,7 @@ func centralize() -> void: func crop_image(image : Image) -> void: + Global.canvas.selection.move_content_confirm() # Use first cel as a starting rectangle var used_rect : Rect2 = image.get_used_rect() diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 01a2d7ec390d..696dab3b090c 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -360,7 +360,7 @@ func general_redo(project : Project = current_project) -> void: func undo(_frame_index := -1, _layer_index := -1, project : Project = current_project) -> void: general_undo(project) var action_name : String = project.undo_redo.get_current_action_name() - if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": + if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Move Selection" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": if _layer_index > -1 and _frame_index > -1: canvas.update_texture(_layer_index, _frame_index, project) else: @@ -394,7 +394,7 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr func redo(_frame_index := -1, _layer_index := -1, project : Project = current_project) -> void: general_redo(project) var action_name : String = project.undo_redo.get_current_action_name() - if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": + if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Move Selection" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": if _layer_index > -1 and _frame_index > -1: canvas.update_texture(_layer_index, _frame_index, project) else: diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 9f4d711b0bad..eba49cef763a 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -33,6 +33,7 @@ func _ready() -> void: func _about_to_show() -> void: + Global.canvas.selection.move_content_confirm() current_cel = Global.current_project.frames[Global.current_project.current_frame].cels[Global.current_project.current_layer].image current_frame.resize(Global.current_project.size.x, Global.current_project.size.y) current_frame.fill(Color(0, 0, 0, 0)) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index bf964ffd231b..6310552fedd0 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -73,6 +73,19 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> directory_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP) +func commit_undo() -> void: + if Global.canvas.selection.is_moving_content: + Global.canvas.selection.move_content_cancel() + else: + undo_redo.undo() + + +func commit_redo() -> void: + Global.control.redone = true + undo_redo.redo() + Global.control.redone = false + + func select_all_pixels() -> void: clear_selection() for x in size.x: diff --git a/src/Main.gd b/src/Main.gd index 28074af4f71f..c2c37c8f196b 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -162,24 +162,21 @@ func _input(event : InputEvent) -> void: # The section of code below is reserved for Undo and Redo! Do not place code for Input below, but above. if !event.is_echo(): # Checks if the action is pressed down - if event.is_action_pressed("redo_secondary"): # Done, so that "redo_secondary" hasn't - redone = true # a slight delay before it starts. The "redo" and "undo" action don't have a slight delay, - Global.current_project.undo_redo.redo() # The "redo" and "undo" action don't have a slight delay, - redone = false # because they get called as an accelerator once pressed (TopMenuContainer.gd / Line 152). + if event.is_action_pressed("redo_secondary"): + # Done, so that "redo_secondary" hasn't a slight delay before it starts. + # The "redo" and "undo" action don't have a slight delay, + # because they get called as an accelerator once pressed (TopMenuContainer.gd / Line 152). + Global.current_project.commit_redo() return if event.is_action("redo"): # Ctrl + Y - redone = true - Global.current_project.undo_redo.redo() - redone = false + Global.current_project.commit_redo() if event.is_action("redo_secondary"): # Shift + Ctrl + Z - redone = true - Global.current_project.undo_redo.redo() - redone = false + Global.current_project.commit_redo() if event.is_action("undo") and !event.shift: # Ctrl + Z and check if shift isn't pressed - Global.current_project.undo_redo.undo() # so "undo" isn't accidentaly triggered while using "redo_secondary" + Global.current_project.commit_undo() # so "undo" isn't accidentaly triggered while using "redo_secondary" func setup_application_window_size() -> void: diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index 3d37d7c716bf..983a7b534eeb 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -94,6 +94,7 @@ func update_pattern() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() if Global.current_project.layers[Global.current_project.current_layer].locked or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position): return if Global.current_project.selected_pixels and not position in Global.current_project.selected_pixels: diff --git a/src/Tools/Eraser.gd b/src/Tools/Eraser.gd index b73ff86a98f7..f24d2adb20ab 100644 --- a/src/Tools/Eraser.gd +++ b/src/Tools/Eraser.gd @@ -24,6 +24,7 @@ func _init() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask() _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/LightenDarken.gd b/src/Tools/LightenDarken.gd index a5452eb6c1ea..acccf41558c3 100644 --- a/src/Tools/LightenDarken.gd +++ b/src/Tools/LightenDarken.gd @@ -200,6 +200,7 @@ func update_strength() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask(false) _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/Pencil.gd b/src/Tools/Pencil.gd index c139a06cc0fe..967f45bae54d 100644 --- a/src/Tools/Pencil.gd +++ b/src/Tools/Pencil.gd @@ -46,6 +46,7 @@ func update_config() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask() _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 71f8f95fe855..b0e475a6ebe7 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -11,7 +11,7 @@ var undo_data : Dictionary func draw_start(position : Vector2) -> void: - Global.canvas.selection.move_content_end() + Global.canvas.selection.move_content_confirm() undo_data = Global.canvas.selection._get_undo_data(false) for selection in Global.current_project.selections: if selection.rect_outline.has_point(position): diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 637b23e1ecf5..5eacba962258 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -24,6 +24,7 @@ class SelectionPolygon: var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed var move_preview_location := Vector2.ZERO +var is_moving_content := false var preview_image := Image.new() var preview_image_texture : ImageTexture var undo_data : Dictionary @@ -37,6 +38,15 @@ func _ready() -> void: tween.start() +func _input(event : InputEvent): + if event is InputEventKey: + if is_moving_content: + if event.scancode == 16777221: + move_content_confirm() + elif event.scancode == 16777217: + move_content_cancel() + + func _offset_tween_completed(_object, _key) -> void: self.line_offset = Vector2.ZERO tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) @@ -140,8 +150,10 @@ func clip_selections(polygon : SelectionPolygon, polygon_pixels : Array) -> void func move_content_start() -> void: -# move_borders_start() - get_preview_image() + if !is_moving_content: + is_moving_content = true + undo_data = _get_undo_data(true) + get_preview_image() func move_content(move : Vector2) -> void: @@ -149,15 +161,40 @@ func move_content(move : Vector2) -> void: move_preview_location += move -func move_content_end() -> void: - if preview_image.is_empty(): +func move_content_confirm() -> void: + if !is_moving_content: return var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) Global.canvas.update_texture(project.current_layer) + var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() + for i in selected_pixels_copy.size(): + selected_pixels_copy[i] += move_preview_location + Global.current_project.selected_pixels = selected_pixels_copy preview_image = Image.new() move_preview_location = Vector2.ZERO + is_moving_content = false + commit_undo("Move Selection", undo_data) + + +func move_content_cancel() -> void: + if preview_image.is_empty(): + return + for polygon in Global.current_project.selections: + polygon.rect_outline.position -= move_preview_location + var borders_copy = polygon.border.duplicate() + for i in borders_copy.size(): + borders_copy[i] -= move_preview_location + polygon.border = borders_copy + + move_preview_location = Vector2.ZERO + is_moving_content = false + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) + Global.canvas.update_texture(project.current_layer) + preview_image = Image.new() func commit_undo(action : String, _undo_data : Dictionary) -> void: @@ -168,8 +205,10 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: project.undo_redo.create_action(action) project.undo_redo.add_do_property(project, "selections", redo_data["selections"]) project.undo_redo.add_do_property(project, "selected_pixels", redo_data["selected_pixels"]) + project.undo_redo.add_undo_property(project, "selections", _undo_data["selections"]) project.undo_redo.add_undo_property(project, "selected_pixels", _undo_data["selected_pixels"]) + var i := 0 for polygon in Global.current_project.selections: if "border_%s" % i in _undo_data: @@ -246,7 +285,7 @@ func _draw() -> void: draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), radius, Color.gray) - if !preview_image.is_empty(): + if is_moving_content and !preview_image.is_empty(): draw_texture(preview_image_texture, move_preview_location, Color(1, 1, 1, 0.5)) @@ -328,22 +367,21 @@ func get_bounding_rectangle(borders : Array) -> Rect2: func get_preview_image() -> void: - if !preview_image.is_empty(): - return var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - preview_image.copy_from(cel_image) - preview_image.lock() - for x in range(0, project.size.x): - for y in range(0, project.size.y): - var pos := Vector2(x, y) - if not pos in project.selected_pixels: - preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) - preview_image.unlock() - preview_image_texture = ImageTexture.new() - preview_image_texture.create_from_image(preview_image, 0) + if preview_image.is_empty(): + preview_image.copy_from(cel_image) + preview_image.lock() + for x in range(0, project.size.x): + for y in range(0, project.size.y): + var pos := Vector2(x, y) + if not pos in project.selected_pixels: + preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) + preview_image.unlock() + preview_image_texture = ImageTexture.new() + preview_image_texture.create_from_image(preview_image, 0) var clear_image := Image.new() clear_image.create(preview_image.get_width(), preview_image.get_height(), false, Image.FORMAT_RGBA8) - cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), Vector2.ZERO) + cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) Global.canvas.update_texture(project.current_layer) diff --git a/src/UI/Dialogs/ExportDialog.gd b/src/UI/Dialogs/ExportDialog.gd index c54b21158a43..0186a2bce675 100644 --- a/src/UI/Dialogs/ExportDialog.gd +++ b/src/UI/Dialogs/ExportDialog.gd @@ -222,6 +222,7 @@ func set_export_progress_bar(value: float) -> void: func _on_ExportDialog_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() # If we're on HTML5, don't let the user change the directory path if OS.get_name() == "HTML5": path_container.visible = false diff --git a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd index ee26c695aa67..198cabf09dd1 100644 --- a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd +++ b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd @@ -16,6 +16,7 @@ onready var preview_rect : TextureRect = $VBoxContainer/Preview func _on_ResizeCanvas_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() image = Image.new() image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) image.lock() diff --git a/src/UI/Dialogs/ImageEffects/ScaleImage.gd b/src/UI/Dialogs/ImageEffects/ScaleImage.gd index 89b5df28856f..1d4ac430bc50 100644 --- a/src/UI/Dialogs/ImageEffects/ScaleImage.gd +++ b/src/UI/Dialogs/ImageEffects/ScaleImage.gd @@ -12,6 +12,7 @@ onready var ratio_box : BaseButton = find_node("AspectRatioButton") func _on_ScaleImage_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() width_value.value = Global.current_project.size.x height_value.value = Global.current_project.size.y width_value_perc.value = 100 diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index c82c2c25ba09..fbf95744e338 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -259,11 +259,9 @@ func on_recent_projects_submenu_id_pressed(id : int) -> void: func edit_menu_id_pressed(id : int) -> void: match id: EditMenuId.UNDO: - Global.current_project.undo_redo.undo() + Global.current_project.commit_undo() EditMenuId.REDO: - Global.control.redone = true - Global.current_project.undo_redo.redo() - Global.control.redone = false + Global.current_project.commit_redo() EditMenuId.COPY: pass # Global.selection_rectangl.copy() From 3db897cd2a8736d75fc1a2aedc117107b0c0eb3c Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 13 Mar 2021 03:02:47 +0200 Subject: [PATCH 18/69] Mirror View affects selection --- src/UI/Canvas/Selection.gd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 5eacba962258..4e9c6aed1fa5 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -251,6 +251,12 @@ func _get_undo_data(undo_image : bool) -> Dictionary: func _draw() -> void: + var _position := position + var _scale := scale + if Global.mirror_view: + _position.x = _position.x + Global.current_project.size.x + _scale.x = -1 + draw_set_transform(_position, rotation, _scale) for p in Global.current_project.selections: var points : Array = p.border for i in range(1, points.size() + 1): @@ -287,6 +293,7 @@ func _draw() -> void: if is_moving_content and !preview_image.is_empty(): draw_texture(preview_image_texture, move_preview_location, Color(1, 1, 1, 0.5)) + draw_set_transform(position, rotation, scale) # Taken and modified from https://github.com/juddrgledhill/godot-dashed-line From 4f49e30d9d1e13bb5e28331f8a2ec5f3b2a6408d Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 14 Mar 2021 21:14:37 +0200 Subject: [PATCH 19/69] Restore Cut, Copy, Paste and Clear Selection Pasting now no longer requires a pre-existing selection and instead copies the selections themselves too. --- src/Autoload/Global.gd | 1 + src/Classes/Project.gd | 1 + src/Tools/RectSelect.gd | 7 +-- src/UI/Canvas/Selection.gd | 88 +++++++++++++++++++++++++++++++++++++- src/UI/TopMenuContainer.gd | 16 +++---- 5 files changed, 95 insertions(+), 18 deletions(-) diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 696dab3b090c..a6d4d47f89b9 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -430,6 +430,7 @@ func title_changed(value : String) -> void: func project_changed(value : int) -> void: + canvas.selection.move_content_confirm() current_project_index = value current_project = projects[value] current_project.change_project() diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 6310552fedd0..88985592dc39 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -596,6 +596,7 @@ func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 +## Experiments, unused for now func get_selection_polygon() -> PoolVector2Array: if !selected_pixels: return PoolVector2Array() diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index b0e475a6ebe7..c5a3067aba7a 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -18,14 +18,11 @@ func draw_start(position : Vector2) -> void: current_selection = selection if !current_selection: - var selections : Array = Global.current_project.selections.duplicate() if !Tools.shift and !Tools.control: - var selected_pixels : Array = Global.current_project.selected_pixels.duplicate() - selected_pixels.clear() - Global.current_project.selected_pixels = selected_pixels - selections.clear() + Global.canvas.selection.clear_selection() _start = Rect2(position, Vector2.ZERO) var new_selection = Global.canvas.selection.SelectionPolygon.new(_start) + var selections : Array = Global.current_project.selections.duplicate() selections.append(new_selection) current_selection = new_selection Global.current_project.selections = selections diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 4e9c6aed1fa5..169b2d080a81 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -4,7 +4,6 @@ extends Node2D class SelectionPolygon: var border := [] var rect_outline : Rect2 -# var rects := [] # Array of Rect2s func _init(rect : Rect2) -> void: rect_outline = rect @@ -21,6 +20,15 @@ class SelectionPolygon: border[2] = rect.end border[3] = Vector2(rect.position.x, rect.end.y) + +class Clipboard: + var image := Image.new() + var polygons := [] # Array of SelectionPolygons + var position := Vector2.ZERO + var selected_pixels := [] + + +var clipboard := Clipboard.new() var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed var move_preview_location := Vector2.ZERO @@ -167,7 +175,6 @@ func move_content_confirm() -> void: var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) - Global.canvas.update_texture(project.current_layer) var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() for i in selected_pixels_copy.size(): selected_pixels_copy[i] += move_preview_location @@ -250,6 +257,73 @@ func _get_undo_data(undo_image : bool) -> Dictionary: return data +func cut() -> void: + copy() + delete() + + +func copy() -> void: + var project := Global.current_project + if project.selected_pixels.empty(): + return + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + var selection_rectangle := get_big_bounding_rectangle() + var to_copy := Image.new() + to_copy = image.get_rect(selection_rectangle) + to_copy.lock() + if project.selections.size() > 1 or project.selections[0].border.size() > 4: + # Only remove unincluded pixels if the selection is not a single rectangle + for x in to_copy.get_size().x: + for y in to_copy.get_size().y: + var pos := Vector2(x, y) + if not (pos + selection_rectangle.position) in project.selected_pixels: + to_copy.set_pixelv(pos, Color(0)) + to_copy.unlock() + clipboard.image = to_copy + for selection in project.selections: + var selection_duplicate := SelectionPolygon.new(selection.rect_outline) + selection_duplicate.border = selection.border + clipboard.polygons.append(selection_duplicate) + clipboard.position = selection_rectangle.position + clipboard.selected_pixels = project.selected_pixels.duplicate() + + +func paste() -> void: + if !clipboard.image: + return + var _undo_data = _get_undo_data(true) + var project := Global.current_project + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + clear_selection() + project.selections = clipboard.polygons.duplicate() + project.selected_pixels = clipboard.selected_pixels.duplicate() + image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), clipboard.position) + commit_undo("Draw", _undo_data) + + +func delete() -> void: + var project := Global.current_project + if project.selected_pixels.empty(): + return + var _undo_data = _get_undo_data(true) + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + for pixel in project.selected_pixels: + image.set_pixelv(pixel, Color(0)) + commit_undo("Draw", _undo_data) + + +func clear_selection(use_undo := false) -> void: + var _undo_data = _get_undo_data(false) + var selections : Array = Global.current_project.selections.duplicate() + var selected_pixels : Array = Global.current_project.selected_pixels.duplicate() + selected_pixels.clear() + Global.current_project.selected_pixels = selected_pixels + selections.clear() + Global.current_project.selections = selections + if use_undo: + commit_undo("Clear Selection", _undo_data) + + func _draw() -> void: var _position := position var _scale := scale @@ -392,3 +466,13 @@ func get_preview_image() -> void: clear_image.create(preview_image.get_width(), preview_image.get_height(), false, Image.FORMAT_RGBA8) cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) Global.canvas.update_texture(project.current_layer) + + +func get_big_bounding_rectangle() -> Rect2: + # Returns a rectangle that contains the entire selection, with multiple polygons + var project : Project = Global.current_project + var rect := Rect2() + rect = project.selections[0].rect_outline + for i in range(1, project.selections.size()): + rect = rect.merge(project.selections[i].rect_outline) + return rect diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index fbf95744e338..bbafe43dad64 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -263,21 +263,15 @@ func edit_menu_id_pressed(id : int) -> void: EditMenuId.REDO: Global.current_project.commit_redo() EditMenuId.COPY: - pass -# Global.selection_rectangl.copy() + Global.canvas.selection.copy() EditMenuId.CUT: - pass -# Global.selection_rectangl.cut() + Global.canvas.selection.cut() EditMenuId.PASTE: - pass -# Global.selection_rectangl.paste() + Global.canvas.selection.paste() EditMenuId.DELETE: - pass -# Global.selection_rectangl.delete() + Global.canvas.selection.delete() EditMenuId.CLEAR_SELECTION: - pass -# Global.selection_rectangl.set_rect(Rect2(0, 0, 0, 0)) -# Global.selection_rectangl.select_rect() + Global.canvas.selection.clear_selection(true) EditMenuId.PREFERENCES: Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.dialog_open(true) From a05b73c9c95beab90652f70500e273cf0cdf4582 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 18 Mar 2021 20:45:41 +0200 Subject: [PATCH 20/69] Created a new Select menu, which has Select All and Clear Selection as options Clear Selection now also confirms content moving. TopMenuContainer code has changed to no longer rely on Global for the menu buttons. --- project.godot | 5 ++++ src/Autoload/Export.gd | 2 +- src/Autoload/Global.gd | 10 ------- src/Autoload/OpenSave.gd | 6 ++-- src/Classes/Project.gd | 19 +++--------- src/Main.gd | 4 +-- src/Tools/RectSelect.gd | 21 -------------- src/UI/Canvas/Selection.gd | 14 +++++++++ src/UI/TopMenuContainer.gd | 56 +++++++++++++++++++++++++++++------- src/UI/TopMenuContainer.tscn | 22 +++++++++----- 10 files changed, 90 insertions(+), 69 deletions(-) diff --git a/project.godot b/project.godot index dbb7251aef41..90caf308044b 100644 --- a/project.godot +++ b/project.godot @@ -498,6 +498,11 @@ right_move_tool={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) ] } +select_all={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Autoload/Export.gd b/src/Autoload/Export.gd index 607895e021e4..e41884795ea0 100644 --- a/src/Autoload/Export.gd +++ b/src/Autoload/Export.gd @@ -196,7 +196,7 @@ func export_processed_images(ignore_overwrites: bool, export_dialog: AcceptDialo # Store settings for quick export and when the dialog is opened again was_exported = true Global.current_project.was_exported = true - Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format))) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format))) # Only show when not exporting gif - gif export finishes in thread if not (current_tab == ExportTab.ANIMATION and animation_type == AnimationType.ANIMATED): diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index a6d4d47f89b9..50fbed99f5b7 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -118,11 +118,6 @@ var horizontal_ruler : BaseButton var vertical_ruler : BaseButton var transparent_checker : ColorRect -var file_menu : MenuButton -var edit_menu : MenuButton -var view_menu : MenuButton -var image_menu : MenuButton -var help_menu : MenuButton var cursor_position_label : Label var zoom_level_label : Label @@ -221,11 +216,6 @@ func _ready() -> void: vertical_ruler = find_node_by_name(root, "VerticalRuler") transparent_checker = find_node_by_name(root, "TransparentChecker") - file_menu = find_node_by_name(root, "FileMenu") - edit_menu = find_node_by_name(root, "EditMenu") - view_menu = find_node_by_name(root, "ViewMenu") - image_menu = find_node_by_name(root, "ImageMenu") - help_menu = find_node_by_name(root, "HelpMenu") cursor_position_label = find_node_by_name(root, "CursorPosition") zoom_level_label = find_node_by_name(root, "ZoomLevel") diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 9fabf9730e1c..a073c3649520 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -119,8 +119,8 @@ func open_pxo_file(path : String, untitled_backup : bool = false, replace_empty new_project.directory_path = Export.directory_path new_project.file_name = Export.file_name Export.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % path.get_file()) - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % path.get_file()) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) Global.save_project_to_recent_list(path) @@ -339,7 +339,7 @@ func save_pxo_file(path : String, autosave : bool, use_zstd_compression := true, Export.directory_path = path.get_base_dir() Export.was_exported = false project.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % path.get_file()) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % path.get_file()) Global.save_project_to_recent_list(path) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 88985592dc39..5125df55e1d0 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -86,17 +86,6 @@ func commit_redo() -> void: Global.control.redone = false -func select_all_pixels() -> void: - clear_selection() - for x in size.x: - for y in size.y: - selected_pixels.append(Vector2(x, y)) - - -func clear_selection() -> void: - selected_pixels.clear() - - func _set_selections(value : Array) -> void: selections = value # Global.selection_rectangl.set_rect(value) @@ -204,9 +193,9 @@ func change_project() -> void: if save_path != "": Global.open_sprites_dialog.current_path = save_path Global.save_sprites_dialog.current_path = save_path - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % save_path.get_file()) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % save_path.get_file()) else: - Global.file_menu.get_popup().set_item_text(4, tr("Save")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save")) Export.directory_path = directory_path Export.file_name = file_name @@ -214,9 +203,9 @@ func change_project() -> void: Export.was_exported = was_exported if !was_exported: - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) else: - Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))) for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) diff --git a/src/Main.gd b/src/Main.gd index c2c37c8f196b..91ffd5c48e30 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -346,8 +346,8 @@ func _on_BackupConfirmation_confirmed(project_paths : Array, backup_paths : Arra Export.file_name = OpenSave.current_save_paths[0].get_file().trim_suffix(".pxo") Export.directory_path = OpenSave.current_save_paths[0].get_base_dir() Export.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) func _on_BackupConfirmation_delete(project_paths : Array, backup_paths : Array) -> void: diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index c5a3067aba7a..22c56acef0ed 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -26,32 +26,11 @@ func draw_start(position : Vector2) -> void: selections.append(new_selection) current_selection = new_selection Global.current_project.selections = selections -# for selection in Global.current_project.selections: -# selection.queue_free() -# current_selection_id = 0 -# else: -# current_selection_id = Global.current_project.selections.size() -# var selection_shape := preload("res://src/Tools/SelectionShape.tscn").instance() -# current_selection = selection_shape -## Global.current_project.selections.append(selection_shape) -# Global.canvas.selection.add_child(selection_shape) -# _start = Rect2(position, Vector2.ZERO) -# selection_shape.set_rect(_start) else: _move = true _offset = position start_position = position Global.canvas.selection.move_borders_start() -# _set_cursor_text(selection.get_rect()) -# if Global.selection_rectangle.has_point(position): -# _move = true -# _offset = position -# Global.selection_rectangle.move_start(Tools.shift) -# _set_cursor_text(Global.selection_rectangle.get_rect()) -# else: -# _drag = true -# _start = Rect2(position, Vector2.ZERO) -# Global.selection_rectangle.set_rect(_start) func draw_move(position : Vector2) -> void: diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 169b2d080a81..ae66d3ce9b3f 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -312,7 +312,21 @@ func delete() -> void: commit_undo("Draw", _undo_data) +func select_all() -> void: + var project := Global.current_project + var _undo_data = _get_undo_data(false) + clear_selection() + var full_rect = Rect2(Vector2.ZERO, project.size) + var new_selection = SelectionPolygon.new(full_rect) + var selections : Array = project.selections.duplicate() + selections.append(new_selection) + project.selections = selections + select_rect() + commit_undo("Rectangle Select", _undo_data) + + func clear_selection(use_undo := false) -> void: + move_content_confirm() var _undo_data = _get_undo_data(false) var selections : Array = Global.current_project.selections.duplicate() var selected_pixels : Array = Global.current_project.selected_pixels.duplicate() diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index bbafe43dad64..6f30e86db9d0 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -2,21 +2,37 @@ extends Panel enum FileMenuId {NEW, OPEN, OPEN_LAST_PROJECT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, QUIT} -enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, CLEAR_SELECTION, PREFERENCES} +enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, PREFERENCES} enum ViewMenuId {TILE_MODE, WINDOW_TRANSPARENCY, PANEL_LAYOUT, MIRROR_VIEW, SHOW_GRID, SHOW_PIXEL_GRID, SHOW_RULERS, SHOW_GUIDES, SHOW_ANIMATION_TIMELINE, ZEN_MODE, FULLSCREEN_MODE} -enum ImageMenuId {SCALE_IMAGE,CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} +enum ImageMenuId {SCALE_IMAGE, CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} +enum SelectMenuId {SELECT_ALL, CLEAR_SELECTION} enum HelpMenuId {VIEW_SPLASH_SCREEN, ONLINE_DOCS, ISSUE_TRACKER, CHANGELOG, ABOUT_PIXELORAMA} +var file_menu_button : MenuButton +var edit_menu_button : MenuButton +var view_menu_button : MenuButton +var image_menu_button : MenuButton +var select_menu_button : MenuButton +var help_menu_button : MenuButton + var file_menu : PopupMenu var view_menu : PopupMenu var zen_mode := false func _ready() -> void: + file_menu_button = find_node("FileMenu") + edit_menu_button = find_node("EditMenu") + view_menu_button = find_node("ViewMenu") + image_menu_button = find_node("ImageMenu") + select_menu_button = find_node("SelectMenu") + help_menu_button = find_node("HelpMenu") + setup_file_menu() setup_edit_menu() setup_view_menu() setup_image_menu() + setup_select_menu() setup_help_menu() @@ -32,7 +48,7 @@ func setup_file_menu() -> void: "Export as..." : InputMap.get_action_list("export_file_as")[0].get_scancode_with_modifiers(), "Quit" : InputMap.get_action_list("quit")[0].get_scancode_with_modifiers(), } - file_menu = Global.file_menu.get_popup() + file_menu = file_menu_button.get_popup() var i := 0 for item in file_menu_items.keys(): @@ -65,10 +81,9 @@ func setup_edit_menu() -> void: "Cut" : InputMap.get_action_list("cut")[0].get_scancode_with_modifiers(), "Paste" : InputMap.get_action_list("paste")[0].get_scancode_with_modifiers(), "Delete" : InputMap.get_action_list("delete")[0].get_scancode_with_modifiers(), - "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), "Preferences" : 0 } - var edit_menu : PopupMenu = Global.edit_menu.get_popup() + var edit_menu : PopupMenu = edit_menu_button.get_popup() var i := 0 for item in edit_menu_items.keys(): @@ -92,7 +107,7 @@ func setup_view_menu() -> void: "Zen Mode" : InputMap.get_action_list("zen_mode")[0].get_scancode_with_modifiers(), "Fullscreen Mode" : InputMap.get_action_list("toggle_fullscreen")[0].get_scancode_with_modifiers(), } - view_menu = Global.view_menu.get_popup() + view_menu = view_menu_button.get_popup() var i := 0 for item in view_menu_items.keys(): @@ -147,7 +162,7 @@ func setup_image_menu() -> void: "Gradient" : 0, # "Shader" : 0 } - var image_menu : PopupMenu = Global.image_menu.get_popup() + var image_menu : PopupMenu = image_menu_button.get_popup() var i := 0 for item in image_menu_items.keys(): @@ -159,6 +174,21 @@ func setup_image_menu() -> void: image_menu.connect("id_pressed", self, "image_menu_id_pressed") +func setup_select_menu() -> void: + var select_menu_items := { # order as in EditMenuId enum + "Select All" : InputMap.get_action_list("select_all")[0].get_scancode_with_modifiers(), + "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), + } + var select_menu : PopupMenu = select_menu_button.get_popup() + var i := 0 + + for item in select_menu_items.keys(): + select_menu.add_item(item, i, select_menu_items[item]) + i += 1 + + select_menu.connect("id_pressed", self, "select_menu_id_pressed") + + func setup_help_menu() -> void: var help_menu_items := { # order as in HelpMenuId enum "View Splash Screen" : 0, @@ -167,7 +197,7 @@ func setup_help_menu() -> void: "Changelog" : 0, "About Pixelorama" : 0 } - var help_menu : PopupMenu = Global.help_menu.get_popup() + var help_menu : PopupMenu = help_menu_button.get_popup() var i := 0 for item in help_menu_items.keys(): @@ -270,8 +300,6 @@ func edit_menu_id_pressed(id : int) -> void: Global.canvas.selection.paste() EditMenuId.DELETE: Global.canvas.selection.delete() - EditMenuId.CLEAR_SELECTION: - Global.canvas.selection.clear_selection(true) EditMenuId.PREFERENCES: Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.dialog_open(true) @@ -473,6 +501,14 @@ func show_hsv_configuration_popup() -> void: Global.dialog_open(true) +func select_menu_id_pressed(id : int) -> void: + match id: + SelectMenuId.SELECT_ALL: + Global.canvas.selection.select_all() + SelectMenuId.CLEAR_SELECTION: + Global.canvas.selection.clear_selection(true) + + func help_menu_id_pressed(id : int) -> void: match id: HelpMenuId.VIEW_SPLASH_SCREEN: diff --git a/src/UI/TopMenuContainer.tscn b/src/UI/TopMenuContainer.tscn index ad7ecb034e6a..1e9b3bd1d3e5 100644 --- a/src/UI/TopMenuContainer.tscn +++ b/src/UI/TopMenuContainer.tscn @@ -21,7 +21,7 @@ __meta__ = { [node name="FileMenu" type="MenuButton" parent="MenuItems"] margin_right = 35.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "File" switch_on_hover = true @@ -29,7 +29,7 @@ switch_on_hover = true [node name="EditMenu" type="MenuButton" parent="MenuItems"] margin_left = 39.0 margin_right = 75.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Edit" switch_on_hover = true @@ -37,7 +37,7 @@ switch_on_hover = true [node name="ViewMenu" type="MenuButton" parent="MenuItems"] margin_left = 79.0 margin_right = 121.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "View" switch_on_hover = true @@ -45,15 +45,23 @@ switch_on_hover = true [node name="ImageMenu" type="MenuButton" parent="MenuItems"] margin_left = 125.0 margin_right = 177.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Image" switch_on_hover = true -[node name="HelpMenu" type="MenuButton" parent="MenuItems"] +[node name="SelectMenu" type="MenuButton" parent="MenuItems"] margin_left = 181.0 -margin_right = 223.0 -margin_bottom = 23.0 +margin_right = 232.0 +margin_bottom = 20.0 +mouse_default_cursor_shape = 2 +text = "Select" +switch_on_hover = true + +[node name="HelpMenu" type="MenuButton" parent="MenuItems"] +margin_left = 236.0 +margin_right = 278.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Help" switch_on_hover = true From bfa04dfce60428802b25cc6b591c049629c203be Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 19 Mar 2021 04:02:39 +0200 Subject: [PATCH 21/69] Draw gizmos as rectangles No functionality yet. They may need to be turned to nodes, so that they can easily resize based on zoom level and check for mouse enter/exit events. --- src/UI/Canvas/Selection.gd | 45 ++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index ae66d3ce9b3f..0721744dad45 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -3,10 +3,11 @@ extends Node2D class SelectionPolygon: var border := [] - var rect_outline : Rect2 + var rect_outline : Rect2 setget _rect_outline_changed + var gizmos := [Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2()] # Array of Rect2s func _init(rect : Rect2) -> void: - rect_outline = rect + self.rect_outline = rect border.append(rect.position) border.append(Vector2(rect.end.x, rect.position.y)) border.append(rect.end) @@ -14,13 +15,29 @@ class SelectionPolygon: func set_rect(rect : Rect2) -> void: - rect_outline = rect + self.rect_outline = rect border[0] = rect.position border[1] = Vector2(rect.end.x, rect.position.y) border[2] = rect.end border[3] = Vector2(rect.position.x, rect.end.y) + func _rect_outline_changed(value : Rect2) -> void: + rect_outline = value + var rect_pos : Vector2 = rect_outline.position + var rect_end : Vector2 = rect_outline.end + var size := Vector2.ONE + # Clockwise, starting from top-left corner + gizmos[0] = Rect2(rect_pos - size, size) + gizmos[1] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) + gizmos[2] = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) + gizmos[3] = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + gizmos[4] = Rect2(rect_end, size) + gizmos[5] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) + gizmos[6] = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) + gizmos[7] = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + + class Clipboard: var image := Image.new() var polygons := [] # Array of SelectionPolygons @@ -363,20 +380,14 @@ func _draw() -> void: var end := Vector2(end_x, end_y) draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) -# if !polygon.selected_pixels: -# return - var rect_pos : Vector2 = p.rect_outline.position - var rect_end : Vector2 = p.rect_outline.end - var radius := 0.5 - draw_circle(rect_pos, radius, Color.gray) - draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_pos.y), radius, Color.gray) - draw_circle(Vector2(rect_end.x, rect_pos.y), radius, Color.gray) - draw_circle(Vector2(rect_end.x, (rect_end.y + rect_pos.y) / 2), radius, Color.gray) - draw_circle(rect_end, radius, Color.gray) - draw_circle(Vector2(rect_end.x, rect_end.y), radius, Color.gray) - draw_circle(Vector2((rect_end.x + rect_pos.x) / 2, rect_end.y), radius, Color.gray) - draw_circle(Vector2(rect_pos.x, rect_end.y), radius, Color.gray) - draw_circle(Vector2(rect_pos.x, (rect_end.y + rect_pos.y) / 2), radius, Color.gray) + for gizmo in p.gizmos: # Draw gizmos + draw_rect(gizmo, Color.black) + var filled_rect : Rect2 = gizmo + var filled_size := Vector2(0.2, 0.2) + filled_rect.position += filled_size + filled_rect.size -= filled_size * 2 + draw_rect(filled_rect, Color.white) # Filled white square + if is_moving_content and !preview_image.is_empty(): From 6f941ae08f74c4296328e5b80fd07e2cab33b2c4 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 20 Mar 2021 02:12:48 +0200 Subject: [PATCH 22/69] Made gizmos get drawn in the sides and corners of the big bounding rectangle instead of individual selection parts Still no functionality yet. --- src/Classes/Project.gd | 2 ++ src/UI/Canvas/Selection.gd | 65 +++++++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 5125df55e1d0..ede49471b789 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -210,6 +210,8 @@ func change_project() -> void: for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) + Global.canvas.selection.big_bounding_rectangle = Global.canvas.selection.get_big_bounding_rectangle() + func serialize() -> Dictionary: var layer_data := [] diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 0721744dad45..471b82303b9d 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -3,8 +3,8 @@ extends Node2D class SelectionPolygon: var border := [] - var rect_outline : Rect2 setget _rect_outline_changed - var gizmos := [Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2()] # Array of Rect2s + var rect_outline : Rect2 + func _init(rect : Rect2) -> void: self.rect_outline = rect @@ -22,27 +22,12 @@ class SelectionPolygon: border[3] = Vector2(rect.position.x, rect.end.y) - func _rect_outline_changed(value : Rect2) -> void: - rect_outline = value - var rect_pos : Vector2 = rect_outline.position - var rect_end : Vector2 = rect_outline.end - var size := Vector2.ONE - # Clockwise, starting from top-left corner - gizmos[0] = Rect2(rect_pos - size, size) - gizmos[1] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) - gizmos[2] = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) - gizmos[3] = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) - gizmos[4] = Rect2(rect_end, size) - gizmos[5] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) - gizmos[6] = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) - gizmos[7] = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) - - class Clipboard: var image := Image.new() var polygons := [] # Array of SelectionPolygons var position := Vector2.ZERO var selected_pixels := [] + var big_bounding_rectangle := Rect2() var clipboard := Clipboard.new() @@ -50,9 +35,11 @@ var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed var move_preview_location := Vector2.ZERO var is_moving_content := false +var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed var preview_image := Image.new() var preview_image_texture : ImageTexture var undo_data : Dictionary +var gizmos := [Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2()] # Array of Rect2s func _ready() -> void: @@ -63,7 +50,7 @@ func _ready() -> void: tween.start() -func _input(event : InputEvent): +func _input(event : InputEvent) -> void: if event is InputEventKey: if is_moving_content: if event.scancode == 16777221: @@ -83,11 +70,28 @@ func _offset_changed(value : Vector2) -> void: update() +func _big_bounding_rectangle_changed(value : Rect2) -> void: + big_bounding_rectangle = value + var rect_pos : Vector2 = big_bounding_rectangle.position + var rect_end : Vector2 = big_bounding_rectangle.end + var size := Vector2.ONE + # Clockwise, starting from top-left corner + gizmos[0] = Rect2(rect_pos - size, size) + gizmos[1] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) + gizmos[2] = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) + gizmos[3] = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + gizmos[4] = Rect2(rect_end, size) + gizmos[5] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) + gizmos[6] = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) + gizmos[7] = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + + func move_borders_start() -> void: undo_data = _get_undo_data(false) func move_borders(move : Vector2) -> void: + self.big_bounding_rectangle.position += move for polygon in Global.current_project.selections: polygon.rect_outline.position += move var borders_copy = polygon.border.duplicate() @@ -129,6 +133,7 @@ func select_rect(merge := true) -> void: else: clip_selections(polygon, polygon_pixels) project.selections.erase(polygon) + self.big_bounding_rectangle = get_big_bounding_rectangle() func merge_selections(polygon : SelectionPolygon) -> void: @@ -229,9 +234,11 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: project.undo_redo.create_action(action) project.undo_redo.add_do_property(project, "selections", redo_data["selections"]) project.undo_redo.add_do_property(project, "selected_pixels", redo_data["selected_pixels"]) + project.undo_redo.add_do_property(self, "big_bounding_rectangle", redo_data["big_bounding_rectangle"]) project.undo_redo.add_undo_property(project, "selections", _undo_data["selections"]) project.undo_redo.add_undo_property(project, "selected_pixels", _undo_data["selected_pixels"]) + project.undo_redo.add_undo_property(self, "big_bounding_rectangle", _undo_data["big_bounding_rectangle"]) var i := 0 for polygon in Global.current_project.selections: @@ -259,6 +266,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: var i := 0 data["selections"] = project.selections data["selected_pixels"] = project.selected_pixels + data["big_bounding_rectangle"] = big_bounding_rectangle for polygon in Global.current_project.selections: data["border_%s" % i] = polygon.border data["rect_outline_%s" % i] = polygon.rect_outline @@ -284,25 +292,25 @@ func copy() -> void: if project.selected_pixels.empty(): return var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var selection_rectangle := get_big_bounding_rectangle() var to_copy := Image.new() - to_copy = image.get_rect(selection_rectangle) - to_copy.lock() + to_copy = image.get_rect(big_bounding_rectangle) if project.selections.size() > 1 or project.selections[0].border.size() > 4: + to_copy.lock() # Only remove unincluded pixels if the selection is not a single rectangle for x in to_copy.get_size().x: for y in to_copy.get_size().y: var pos := Vector2(x, y) - if not (pos + selection_rectangle.position) in project.selected_pixels: + if not (pos + big_bounding_rectangle.position) in project.selected_pixels: to_copy.set_pixelv(pos, Color(0)) - to_copy.unlock() + to_copy.unlock() clipboard.image = to_copy for selection in project.selections: var selection_duplicate := SelectionPolygon.new(selection.rect_outline) selection_duplicate.border = selection.border clipboard.polygons.append(selection_duplicate) - clipboard.position = selection_rectangle.position + clipboard.position = big_bounding_rectangle.position clipboard.selected_pixels = project.selected_pixels.duplicate() + clipboard.big_bounding_rectangle = big_bounding_rectangle func paste() -> void: @@ -314,6 +322,7 @@ func paste() -> void: clear_selection() project.selections = clipboard.polygons.duplicate() project.selected_pixels = clipboard.selected_pixels.duplicate() + self.big_bounding_rectangle = clipboard.big_bounding_rectangle image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), clipboard.position) commit_undo("Draw", _undo_data) @@ -351,6 +360,7 @@ func clear_selection(use_undo := false) -> void: Global.current_project.selected_pixels = selected_pixels selections.clear() Global.current_project.selections = selections + self.big_bounding_rectangle = Rect2() if use_undo: commit_undo("Clear Selection", _undo_data) @@ -380,7 +390,8 @@ func _draw() -> void: var end := Vector2(end_x, end_y) draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) - for gizmo in p.gizmos: # Draw gizmos + if big_bounding_rectangle.size > Vector2.ZERO: + for gizmo in gizmos: # Draw gizmos draw_rect(gizmo, Color.black) var filled_rect : Rect2 = gizmo var filled_size := Vector2(0.2, 0.2) @@ -497,6 +508,8 @@ func get_big_bounding_rectangle() -> Rect2: # Returns a rectangle that contains the entire selection, with multiple polygons var project : Project = Global.current_project var rect := Rect2() + if project.selections.size() == 0: + return rect rect = project.selections[0].rect_outline for i in range(1, project.selections.size()): rect = rect.merge(project.selections[i].rect_outline) From 66dc63b0a8ab422749b86e4734b8f7fa4c5b17b8 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 21 Mar 2021 00:52:47 +0200 Subject: [PATCH 23/69] Restore label text --- src/Tools/RectSelect.gd | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 22c56acef0ed..113740cf1a29 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -31,13 +31,14 @@ func draw_start(position : Vector2) -> void: _offset = position start_position = position Global.canvas.selection.move_borders_start() + _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) func draw_move(position : Vector2) -> void: if _move: Global.canvas.selection.move_borders(position - _offset) _offset = position -# _set_cursor_text(selection.get_rect()) + _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) else: var rect := _start.expand(position).abs() rect = rect.grow_individual(0, 0, 1, 1) @@ -51,11 +52,7 @@ func draw_end(position : Vector2) -> void: else: Global.canvas.selection.select_rect(!Tools.control) Global.canvas.selection.commit_undo("Rectangle Select", undo_data) -# var undo_data = Global.canvas.selection._get_undo_data(false) -# current_selection.select_rect(!Tools.control) -# Global.canvas.selection.commit_undo("Rectangle Select", undo_data) -# _drag = false -# Global.canvas.selection.move_borders_end(position, start_position) + _move = false cursor_text = "" start_position = Vector2.INF From 7f685e2ca8befed0c9ac79dfcef8c933166885f4 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 21 Mar 2021 04:37:36 +0200 Subject: [PATCH 24/69] Minor optimization when clipping selections This will execute the for loop less times --- src/UI/Canvas/Selection.gd | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 471b82303b9d..f1bb68f41996 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -155,6 +155,7 @@ func merge_selections(polygon : SelectionPolygon) -> void: func clip_selections(polygon : SelectionPolygon, polygon_pixels : Array) -> void: var to_erase := [] + var to_append := [] for p in Global.current_project.selections: if p == polygon: continue @@ -168,7 +169,7 @@ func clip_selections(polygon : SelectionPolygon, polygon_pixels : Array) -> void var rect = get_bounding_rectangle(arr[i]) var new_polygon := SelectionPolygon.new(rect) new_polygon.border = arr[i] - Global.current_project.selections.append(new_polygon) + to_append.append(new_polygon) var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() for pixel in polygon_pixels: @@ -178,6 +179,9 @@ func clip_selections(polygon : SelectionPolygon, polygon_pixels : Array) -> void for p in to_erase: Global.current_project.selections.erase(p) + for p in to_append: + Global.current_project.selections.append(p) + func move_content_start() -> void: if !is_moving_content: From 31aa7c32166c037f90c901ff7db06427c04900fc Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sun, 21 Mar 2021 21:10:06 +0200 Subject: [PATCH 25/69] Made a Gizmo class, cursor change on hover, has_focus = false on mouse click Now I should actually make them resize when dragged, aye? --- src/UI/Canvas/Selection.gd | 73 +++++++++++++++++++++++++++++++------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index f1bb68f41996..033be659634e 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -30,6 +30,27 @@ class Clipboard: var big_bounding_rectangle := Rect2() +class Gizmo: + var rect : Rect2 + var direction := Vector2.ZERO + + func _init(_direction : Vector2) -> void: + direction = _direction + + + func get_cursor() -> int: + var cursor := Input.CURSOR_MOVE + if direction == Vector2(-1, -1) or direction == Vector2(1, 1): # Top left or bottom right + cursor = Input.CURSOR_FDIAGSIZE + elif direction == Vector2(1, -1) or direction == Vector2(-1, 1): # Top right or bottom left + cursor = Input.CURSOR_BDIAGSIZE + elif direction == Vector2(0, -1) or direction == Vector2(0, 1): # Center top or center bottom + cursor = Input.CURSOR_VSIZE + elif direction == Vector2(-1, 0) or direction == Vector2(1, 0): # Center left or center right + cursor = Input.CURSOR_HSIZE + return cursor + + var clipboard := Clipboard.new() var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed @@ -39,7 +60,8 @@ var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed var preview_image := Image.new() var preview_image_texture : ImageTexture var undo_data : Dictionary -var gizmos := [Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2(), Rect2()] # Array of Rect2s +var gizmos := [] # Array of Gizmos +var is_dragging_gizmo := false func _ready() -> void: @@ -49,14 +71,41 @@ func _ready() -> void: tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) tween.start() + gizmos.append(Gizmo.new(Vector2(-1, -1))) # Top left + gizmos.append(Gizmo.new(Vector2(0, -1))) # Center top + gizmos.append(Gizmo.new(Vector2(1, -1))) # Top right + gizmos.append(Gizmo.new(Vector2(1, 0))) # Center right + gizmos.append(Gizmo.new(Vector2(1, 1))) # Bottom right + gizmos.append(Gizmo.new(Vector2(0, 1))) # Center bottom + gizmos.append(Gizmo.new(Vector2(-1, 1))) # Bottom left + gizmos.append(Gizmo.new(Vector2(-1, 0))) # Center left + func _input(event : InputEvent) -> void: if event is InputEventKey: - if is_moving_content: + if is_moving_content: # Temporary code if event.scancode == 16777221: move_content_confirm() elif event.scancode == 16777217: move_content_cancel() + elif event is InputEventMouse: + var gizmo + for g in gizmos: + if g.rect.has_point(Global.canvas.current_pixel): + gizmo = g + break + if gizmo: + Global.main_viewport.mouse_default_cursor_shape = gizmo.get_cursor() + elif !is_dragging_gizmo: + Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS + + if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: + if gizmo and event.pressed: + Global.has_focus = false + is_dragging_gizmo = true + elif is_dragging_gizmo: + Global.has_focus = true + is_dragging_gizmo = false func _offset_tween_completed(_object, _key) -> void: @@ -76,14 +125,14 @@ func _big_bounding_rectangle_changed(value : Rect2) -> void: var rect_end : Vector2 = big_bounding_rectangle.end var size := Vector2.ONE # Clockwise, starting from top-left corner - gizmos[0] = Rect2(rect_pos - size, size) - gizmos[1] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) - gizmos[2] = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) - gizmos[3] = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) - gizmos[4] = Rect2(rect_end, size) - gizmos[5] = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) - gizmos[6] = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) - gizmos[7] = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + gizmos[0].rect = Rect2(rect_pos - size, size) + gizmos[1].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) + gizmos[2].rect = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) + gizmos[3].rect = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + gizmos[4].rect = Rect2(rect_end, size) + gizmos[5].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) + gizmos[6].rect = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) + gizmos[7].rect = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) func move_borders_start() -> void: @@ -396,8 +445,8 @@ func _draw() -> void: if big_bounding_rectangle.size > Vector2.ZERO: for gizmo in gizmos: # Draw gizmos - draw_rect(gizmo, Color.black) - var filled_rect : Rect2 = gizmo + draw_rect(gizmo.rect, Color.black) + var filled_rect : Rect2 = gizmo.rect var filled_size := Vector2(0.2, 0.2) filled_rect.position += filled_size filled_rect.size -= filled_size * 2 From 937ee23007605a98983cb1d51907aa06036d6da2 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 23 Mar 2021 22:17:13 +0200 Subject: [PATCH 26/69] Very basic gizmo resizing, still a WIP, does not work properly yet --- src/UI/Canvas/Selection.gd | 42 +++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 033be659634e..89db4b138ece 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -61,7 +61,8 @@ var preview_image := Image.new() var preview_image_texture : ImageTexture var undo_data : Dictionary var gizmos := [] # Array of Gizmos -var is_dragging_gizmo := false +var dragged_gizmo : Gizmo = null +var mouse_pos_on_gizmo_drag := Vector2.ZERO func _ready() -> void: @@ -96,16 +97,28 @@ func _input(event : InputEvent) -> void: break if gizmo: Global.main_viewport.mouse_default_cursor_shape = gizmo.get_cursor() - elif !is_dragging_gizmo: + elif !dragged_gizmo: Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: if gizmo and event.pressed: Global.has_focus = false - is_dragging_gizmo = true - elif is_dragging_gizmo: + dragged_gizmo = gizmo + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + elif dragged_gizmo: Global.has_focus = true - is_dragging_gizmo = false + var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction + diff = diff.round() + print(diff) + print(preview_image.get_size()) + var left := 0.0 if dragged_gizmo.direction.x >= 0 else diff.x + var top := 0.0 if dragged_gizmo.direction.y >= 0 else diff.y + var right := diff.x if dragged_gizmo.direction.x >= 0 else 0.0 + var bottom := diff.y if dragged_gizmo.direction.y >= 0 else 0.0 + self.big_bounding_rectangle = big_bounding_rectangle.grow_individual(left, top, right, bottom) + preview_image.resize(big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, Image.INTERPOLATE_NEAREST) + preview_image_texture.create_from_image(preview_image, 0) + dragged_gizmo = null func _offset_tween_completed(_object, _key) -> void: @@ -249,7 +262,7 @@ func move_content_confirm() -> void: return var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() for i in selected_pixels_copy.size(): selected_pixels_copy[i] += move_preview_location @@ -263,6 +276,7 @@ func move_content_confirm() -> void: func move_content_cancel() -> void: if preview_image.is_empty(): return + self.big_bounding_rectangle.position -= move_preview_location for polygon in Global.current_project.selections: polygon.rect_outline.position -= move_preview_location var borders_copy = polygon.border.duplicate() @@ -274,7 +288,7 @@ func move_content_cancel() -> void: is_moving_content = false var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) preview_image = Image.new() @@ -453,9 +467,8 @@ func _draw() -> void: draw_rect(filled_rect, Color.white) # Filled white square - if is_moving_content and !preview_image.is_empty(): - draw_texture(preview_image_texture, move_preview_location, Color(1, 1, 1, 0.5)) + draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) draw_set_transform(position, rotation, scale) @@ -540,12 +553,13 @@ func get_preview_image() -> void: var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image if preview_image.is_empty(): - preview_image.copy_from(cel_image) +# preview_image.copy_from(cel_image) + preview_image = cel_image.get_rect(big_bounding_rectangle) preview_image.lock() - for x in range(0, project.size.x): - for y in range(0, project.size.y): + for x in range(0, big_bounding_rectangle.size.x): + for y in range(0, big_bounding_rectangle.size.y): var pos := Vector2(x, y) - if not pos in project.selected_pixels: + if not (pos + big_bounding_rectangle.position) in project.selected_pixels: preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) preview_image.unlock() preview_image_texture = ImageTexture.new() @@ -553,7 +567,7 @@ func get_preview_image() -> void: var clear_image := Image.new() clear_image.create(preview_image.get_width(), preview_image.get_height(), false, Image.FORMAT_RGBA8) - cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), move_preview_location) + cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) From 9bf8625ac06f34bfd0e64cdeae920a8e6f3b1d8a Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 27 Mar 2021 20:28:20 +0200 Subject: [PATCH 27/69] Start replacing the array of selected pixels with a BitMap This should optimize the selection making a lot, and it also allows for easy border drawing without having to deal with polygons, thanks to the MarchingAntsOutline.shader Still commit is still a WIP, image effects and brushes may not work properly yet. Because the BitMap has a fixed size, the size of the project, moving the selection outside of canvas boundaries has proven to be a bit tricky. I did implement a hacky way of handling it, but it may be buggy and problematic. I'm still unsure whether this is the best way to handle the situation. --- src/Classes/Drawers.gd | 16 +- src/Classes/Project.gd | 169 ++++++------ src/Shaders/MarchingAntsOutline.shader | 52 ++++ src/Tools/BaseTool.gd | 7 +- src/Tools/Bucket.gd | 42 +-- src/Tools/Draw.gd | 24 +- src/Tools/Move.gd | 6 +- src/Tools/RectSelect.gd | 28 +- src/UI/Canvas/CameraMovement.gd | 2 + src/UI/Canvas/Canvas.tscn | 16 +- src/UI/Canvas/Selection.gd | 340 +++++-------------------- 11 files changed, 271 insertions(+), 431 deletions(-) create mode 100644 src/Shaders/MarchingAntsOutline.shader diff --git a/src/Classes/Drawers.gd b/src/Classes/Drawers.gd index 3be9ca9edfc3..83da1c2e61cd 100644 --- a/src/Classes/Drawers.gd +++ b/src/Classes/Drawers.gd @@ -75,19 +75,9 @@ func set_pixel(image: Image, position: Vector2, color: Color) -> void: var mirror_y = project.y_symmetry_point - position.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, position.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(position.x, mirror_y)) + if horizontal_mirror and mirror_x_inside: drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index ede49471b789..445a601746ee 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -22,8 +22,8 @@ var y_symmetry_point var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide +var selection_bitmap := BitMap.new() setget _selection_bitmap_changed var selected_pixels := [] -var selections := [] setget _set_selections # Array of SelectionShape(s) # For every camera (currently there are 3) var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2 @@ -40,6 +40,7 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> frames = _frames name = _name size = _size + selection_bitmap.create(size) update_tile_mode_rects() undo_redo = UndoRedo.new() @@ -86,9 +87,12 @@ func commit_redo() -> void: Global.control.redone = false -func _set_selections(value : Array) -> void: - selections = value -# Global.selection_rectangl.set_rect(value) +func _selection_bitmap_changed(value : BitMap) -> void: + selection_bitmap = value + var image : Image = bitmap_to_image(selection_bitmap) + var image_texture := ImageTexture.new() + image_texture.create_from_image(image, 0) + Global.canvas.selection.marching_ants_outline.texture = image_texture func change_project() -> void: @@ -210,7 +214,8 @@ func change_project() -> void: for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) - Global.canvas.selection.big_bounding_rectangle = Global.canvas.selection.get_big_bounding_rectangle() + self.selection_bitmap = selection_bitmap # call getter method + Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() func serialize() -> Dictionary: @@ -587,71 +592,91 @@ func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 -## Experiments, unused for now -func get_selection_polygon() -> PoolVector2Array: - if !selected_pixels: - return PoolVector2Array() - var polygon := PoolVector2Array() -# var corner = selected_pixels[0] - for pix in selected_pixels: - var polygon_point = pix - var right_n : Vector2 = pix + Vector2.RIGHT - var left_n : Vector2 = pix + Vector2.LEFT - var down_n : Vector2 = pix + Vector2.DOWN - var up_n : Vector2 = pix + Vector2.UP - - var x_axis : bool = right_n in selected_pixels and left_n in selected_pixels - var y_axis : bool = down_n in selected_pixels and up_n in selected_pixels - if x_axis or y_axis: -# if not right_n in selected_pixels and down_n + Vector2.DOWN + Vector2.RIGHT in selected_pixels: -# polygon.append(right_n + Vector2.DOWN) -# else: - continue - else: - if !(right_n in selected_pixels): - polygon_point += Vector2.RIGHT - if !(down_n in selected_pixels): - polygon_point += Vector2.DOWN - polygon.append(polygon_point) - polygon = sort_polygon(polygon) -# print(polygon) - return polygon - - -func sort_polygon(polygon : PoolVector2Array) -> PoolVector2Array: - # Find the center point of the polygon - var x := 0 - var y := 0 - for p in polygon: - x += p.x - y += p.y - var center := Vector2(x / polygon.size(), y / polygon.size()) - - # Sort points by angle from the center - var angles := [] - for p in polygon: - angles.append([p, center.angle_to_point(p)]) - angles.sort_custom(self, "sort_by_angle") - polygon.resize(0) - for p in angles: - polygon.append(p[0]) - return polygon - - -static func sort_by_angle(a, b) -> bool: - if a[1] < b[1]: +func has_selection(bitmap : BitMap = selection_bitmap) -> bool: + return bitmap.get_true_bit_count() > 0 + + +func can_pixel_get_drawn(pixel : Vector2) -> bool: + var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position + if selection_position.x < 0: + pixel.x -= selection_position.x + if selection_position.y < 0: + pixel.y -= selection_position.y + if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: + return false + if has_selection(): + return selection_bitmap.get_bit(pixel) + else: return true - return false - - -#func get_selection_image() -> Image: -# var image := Image.new() -# var cel_image : Image = frames[current_frame].cels[current_layer].image -# image.copy_from(cel_image) -# image.lock() -# image.fill(Color(0, 0, 0, 0)) -# for pixel in selected_pixels: -# var color : Color = cel_image.get_pixelv(pixel) -# image.set_pixelv(pixel, color) -# image.unlock() -# return image + +# Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L605 +func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> void: + if new_size == bitmap.get_size(): + return + var new_bitmap := BitMap.new() + new_bitmap.create(new_size) + var lw = min(bitmap.get_size().x, new_size.x) + var lh = min(bitmap.get_size().y, new_size.y) + for x in lw: + for y in lh: + new_bitmap.set_bit(Vector2(x, y), bitmap.get_bit(Vector2(x, y))) + + bitmap = new_bitmap + + +# Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L622 +func bitmap_to_image(bitmap : BitMap) -> Image: + var image := Image.new() + var width := bitmap.get_size().x + var height := bitmap.get_size().y + image.create(width, height, false, Image.FORMAT_LA8) + image.lock() + for x in width: + for y in height: + var pos := Vector2(x, y) + var color = Color(1, 1, 1, 1) if bitmap.get_bit(pos) else Color(0, 0, 0, 0) + image.set_pixelv(pos, color) + image.unlock() + return image + + +func get_selection_rectangle(bitmap : BitMap = selection_bitmap) -> Rect2: + var rect := Rect2(Vector2.ZERO, Vector2.ZERO) + if has_selection(bitmap): + var image : Image = bitmap_to_image(bitmap) + rect = image.get_used_rect() + return rect + + +#func selection_is_rectangle(bitmap : BitMap = selection_bitmap) -> bool: +# var selection_rect = get_selection_rectangle(bitmap) +# return selection_rect == Global.canvas.selection.big_bounding_rectangle + + +func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: + var selection_node = Global.canvas.selection + var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var image : Image = bitmap_to_image(bitmap) + var selection_rect := image.get_used_rect() + var smaller_image := image.get_rect(selection_rect) + image.lock() + image.fill(Color(0)) + selection_rect.position += to + var dst := selection_position + var x_diff = selection_rect.end.x - size.x + var y_diff = selection_rect.end.y - size.y + var nw = max(size.x, size.x + x_diff) + var nh = max(size.y, size.y + y_diff) + + if selection_position.x < 0: + nw -= selection_position.x + selection_node.marching_ants_outline.offset.x = selection_position.x + dst.x = 0 + if selection_position.y < 0: + nh -= selection_position.y + selection_node.marching_ants_outline.offset.y = selection_position.y + dst.y = 0 + + image.crop(nw, nh) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, size), dst) + bitmap.create_from_image_alpha(image) diff --git a/src/Shaders/MarchingAntsOutline.shader b/src/Shaders/MarchingAntsOutline.shader new file mode 100644 index 000000000000..24970fbd9968 --- /dev/null +++ b/src/Shaders/MarchingAntsOutline.shader @@ -0,0 +1,52 @@ +// Taken and modified from https://godotshaders.com/shader/2d-outline-inline/ +// Also thanks to https://andreashackel.de/tech-art/stripes-shader-1/ for the stripe tutorial +shader_type canvas_item; + +uniform vec4 first_color : hint_color = vec4(1.0); +uniform vec4 second_color : hint_color = vec4(0.0, 0.0, 0.0, 1.0); +uniform bool animated = true; +uniform float width : hint_range(0, 2) = 0.2; +uniform float frequency = 50.0; +uniform float stripe_direction : hint_range(0, 1) = 0.5; + + +bool hasContraryNeighbour(vec2 uv, vec2 texture_pixel_size, sampler2D texture) { + for (float i = -ceil(width); i <= ceil(width); i++) { + float x = abs(i) > width ? width * sign(i) : i; + float offset = width; + + for (float j = -ceil(offset); j <= ceil(offset); j++) { + float y = abs(j) > offset ? offset * sign(j) : j; + vec2 xy = uv + texture_pixel_size * vec2(x, y); + + if ((xy != clamp(xy, vec2(0.0), vec2(1.0)) || texture(texture, xy).a == 0.0) == true) { + return true; + } + } + } + + return false; +} + +void fragment() { + vec2 uv = UV; + COLOR = texture(TEXTURE, uv); + + if ((COLOR.a > 0.0) == true && hasContraryNeighbour(uv, TEXTURE_PIXEL_SIZE, TEXTURE)) { + vec4 final_color = first_color; + // Generate diagonal stripes + if(animated) + uv -= TIME / frequency; + float pos = mix(uv.x, uv.y, stripe_direction) * frequency; + float value = floor(fract(pos) + 0.5); + if (mod(value, 2.0) == 0.0) + final_color = second_color; + + COLOR.rgb = mix(COLOR.rgb, final_color.rgb, final_color.a); + COLOR.a += (1.0 - COLOR.a) * final_color.a; + } + else { + // Erase the texture's pixels in order to only keep the outline visible + COLOR.a = 0.0; + } +} \ No newline at end of file diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd index 66fd6dcc5ec1..379dcdc05480 100644 --- a/src/Tools/BaseTool.gd +++ b/src/Tools/BaseTool.gd @@ -81,11 +81,10 @@ func draw_preview() -> void: func _get_draw_rect() -> Rect2: - if Global.current_project.selected_pixels.empty(): - return Global.current_project.tile_mode_rects[Global.TileMode.NONE] + if Global.current_project.has_selection(): + return Global.current_project.get_selection_rectangle() else: - var selected_pixels = Global.current_project.selected_pixels - return Rect2(selected_pixels[0].x, selected_pixels[0].y, selected_pixels[-1].x - selected_pixels[0].x + 1, selected_pixels[-1].y - selected_pixels[0].y + 1) + return Global.current_project.tile_mode_rects[Global.TileMode.NONE] func _get_draw_image() -> Image: diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index 983a7b534eeb..4e330b91651e 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -97,7 +97,7 @@ func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_confirm() if Global.current_project.layers[Global.current_project.current_layer].locked or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position): return - if Global.current_project.selected_pixels and not position in Global.current_project.selected_pixels: + if Global.current_project.has_selection() and not Global.current_project.selection_bitmap.get_bit(position): return var undo_data = _get_undo_data() if _fill_area == 0: @@ -124,17 +124,14 @@ func fill_in_color(position : Vector2) -> void: return image.lock() - var pixels := [] - if project.selected_pixels: - pixels = project.selected_pixels.duplicate() - else: - for x in Global.current_project.size.x: - for y in Global.current_project.size.y: - pixels.append(Vector2(x, y)) - for i in pixels: - if image.get_pixelv(i).is_equal_approx(color): - _set_pixel(image, i.x, i.y, tool_slot.color) + for x in Global.current_project.size.x: + for y in Global.current_project.size.y: + var pos := Vector2(x, y) + if project.has_selection() and not project.selection_bitmap.get_bit(pos): + continue + if image.get_pixelv(pos).is_equal_approx(color): + _set_pixel(image, x, y, tool_slot.color) func fill_in_area(position : Vector2) -> void: @@ -146,19 +143,9 @@ func fill_in_area(position : Vector2) -> void: var mirror_y = project.y_symmetry_point - position.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, position.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(position.x, mirror_y)) if tool_slot.horizontal_mirror and mirror_x_inside: _flood_fill(Vector2(mirror_x, position.y)) @@ -203,13 +190,8 @@ func _flood_fill(position : Vector2) -> void: func _set_pixel(image : Image, x : int, y : int, color : Color) -> void: var project : Project = Global.current_project - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - if not _get_draw_rect().has_point(Vector2(x, y)): - return - else: - if not Vector2(x, y) in project.selected_pixels: - return + if !project.can_pixel_get_drawn(Vector2(x, y)): + return if _fill_with == 0 or _pattern == null: image.set_pixel(x, y, color) diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 776fc9b08b9a..f564674dcb50 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -281,19 +281,8 @@ func draw_tool_brush(position : Vector2) -> void: var mirror_y = (project.y_symmetry_point + 1) - dst.y - src_rect.size.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, dst.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(dst.x, mirror_y)) if tool_slot.horizontal_mirror and mirror_x_inside: _draw_brush_image(_mirror_brushes.x, _flip_rect(src_rect, size, true, false), Vector2(mirror_x, dst.y)) @@ -337,13 +326,8 @@ func _set_pixel(position : Vector2) -> void: if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = position.posmodv(project.size) - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - if not _get_draw_rect().has_point(position): - return - else: - if not position in project.selected_pixels: - return + if !project.can_pixel_get_drawn(position): + return var image := _get_draw_image() var i := int(position.x + position.y * image.get_size().x) diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index 4d83122c53b2..3b400243b9cd 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -8,12 +8,12 @@ var offset : Vector2 func draw_start(position : Vector2) -> void: starting_pos = position offset = position - if Global.current_project.selected_pixels: + if Global.current_project.has_selection(): Global.canvas.selection.move_content_start() func draw_move(position : Vector2) -> void: - if Global.current_project.selected_pixels: + if Global.current_project.has_selection(): Global.canvas.selection.move_content(position - offset) offset = position else: @@ -28,7 +28,7 @@ func draw_end(position : Vector2) -> void: var project : Project = Global.current_project var image : Image = _get_draw_image() - if project.selected_pixels: + if project.has_selection(): pass # Global.canvas.selection.move_content_end() # Global.canvas.selection.move_borders_end(position, starting_pos) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 113740cf1a29..f875ce35003a 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -1,8 +1,8 @@ extends BaseTool -var current_selection var start_position := Vector2.INF +var rect := Rect2(0, 0, 0, 0) var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO var _drag := false @@ -13,19 +13,12 @@ var undo_data : Dictionary func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_confirm() undo_data = Global.canvas.selection._get_undo_data(false) - for selection in Global.current_project.selections: - if selection.rect_outline.has_point(position): - current_selection = selection - if !current_selection: + if !Global.canvas.selection.big_bounding_rectangle.has_point(position): if !Tools.shift and !Tools.control: Global.canvas.selection.clear_selection() _start = Rect2(position, Vector2.ZERO) - var new_selection = Global.canvas.selection.SelectionPolygon.new(_start) - var selections : Array = Global.current_project.selections.duplicate() - selections.append(new_selection) - current_selection = new_selection - Global.current_project.selections = selections + else: _move = true _offset = position @@ -40,9 +33,8 @@ func draw_move(position : Vector2) -> void: _offset = position _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) else: - var rect := _start.expand(position).abs() + rect = _start.expand(position).abs() rect = rect.grow_individual(0, 0, 1, 1) - current_selection.set_rect(rect) _set_cursor_text(rect) @@ -50,13 +42,13 @@ func draw_end(position : Vector2) -> void: if _move: Global.canvas.selection.move_borders_end(position, start_position) else: - Global.canvas.selection.select_rect(!Tools.control) + Global.canvas.selection.select_rect(rect, !Tools.control) Global.canvas.selection.commit_undo("Rectangle Select", undo_data) _move = false cursor_text = "" start_position = Vector2.INF - current_selection = null + rect = Rect2(0, 0, 0, 0) func cursor_move(_position : Vector2) -> void: @@ -72,7 +64,7 @@ func cursor_move(_position : Vector2) -> void: # Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS -func _set_cursor_text(rect : Rect2) -> void: - cursor_text = "%s, %s" % [rect.position.x, rect.position.y] - cursor_text += " -> %s, %s" % [rect.end.x - 1, rect.end.y - 1] - cursor_text += " (%s, %s)" % [rect.size.x, rect.size.y] +func _set_cursor_text(_rect : Rect2) -> void: + cursor_text = "%s, %s" % [_rect.position.x, _rect.position.y] + cursor_text += " -> %s, %s" % [_rect.end.x - 1, _rect.end.y - 1] + cursor_text += " (%s, %s)" % [_rect.size.x, _rect.size.y] diff --git a/src/UI/Canvas/CameraMovement.gd b/src/UI/Canvas/CameraMovement.gd index 689149654c53..a90008355490 100644 --- a/src/UI/Canvas/CameraMovement.gd +++ b/src/UI/Canvas/CameraMovement.gd @@ -178,6 +178,8 @@ func zoom_changed() -> void: update_rulers() for guide in Global.current_project.guides: guide.width = zoom.x * 2 + Global.canvas.selection.marching_ants_outline.material.set_shader_param("width", zoom.x) + Global.canvas.selection.marching_ants_outline.material.set_shader_param("frequency", (1.0 / zoom.x) * 10) elif name == "CameraPreview": Global.preview_zoom_slider.value = -zoom.x diff --git a/src/UI/Canvas/Canvas.tscn b/src/UI/Canvas/Canvas.tscn index cc46ac1da9ab..9d7142fb56ee 100644 --- a/src/UI/Canvas/Canvas.tscn +++ b/src/UI/Canvas/Canvas.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=10 format=2] +[gd_scene load_steps=12 format=2] [ext_resource path="res://src/UI/Canvas/Canvas.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/Grid.gd" type="Script" id=2] @@ -8,10 +8,20 @@ [ext_resource path="res://src/UI/Canvas/PixelGrid.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/Previews.gd" type="Script" id=7] [ext_resource path="res://src/UI/Canvas/Selection.gd" type="Script" id=8] +[ext_resource path="res://src/Shaders/MarchingAntsOutline.shader" type="Shader" id=9] [sub_resource type="CanvasItemMaterial" id=1] blend_mode = 4 +[sub_resource type="ShaderMaterial" id=2] +shader = ExtResource( 8 ) +shader_param/first_color = Color( 1, 1, 1, 1 ) +shader_param/second_color = Color( 0, 0, 0, 1 ) +shader_param/animated = true +shader_param/width = 0.05 +shader_param/frequency = 200.0 +shader_param/stripe_direction = 0.5 + [node name="Canvas" type="Node2D"] script = ExtResource( 1 ) @@ -40,6 +50,10 @@ script = ExtResource( 2 ) [node name="Selection" type="Node2D" parent="."] script = ExtResource( 7 ) +[node name="MarchingAntsOutline" type="Sprite" parent="Selection"] +material = SubResource( 2 ) +centered = false + [node name="Indicators" type="Node2D" parent="."] script = ExtResource( 3 ) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 89db4b138ece..4fbb614a6129 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -1,32 +1,9 @@ extends Node2D -class SelectionPolygon: - var border := [] - var rect_outline : Rect2 - - - func _init(rect : Rect2) -> void: - self.rect_outline = rect - border.append(rect.position) - border.append(Vector2(rect.end.x, rect.position.y)) - border.append(rect.end) - border.append(Vector2(rect.position.x, rect.end.y)) - - - func set_rect(rect : Rect2) -> void: - self.rect_outline = rect - border[0] = rect.position - border[1] = Vector2(rect.end.x, rect.position.y) - border[2] = rect.end - border[3] = Vector2(rect.position.x, rect.end.y) - - class Clipboard: var image := Image.new() - var polygons := [] # Array of SelectionPolygons - var position := Vector2.ZERO - var selected_pixels := [] + var selection_bitmap := BitMap.new() var big_bounding_rectangle := Rect2() @@ -54,7 +31,6 @@ class Gizmo: var clipboard := Clipboard.new() var tween : Tween var line_offset := Vector2.ZERO setget _offset_changed -var move_preview_location := Vector2.ZERO var is_moving_content := false var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed var preview_image := Image.new() @@ -64,6 +40,8 @@ var gizmos := [] # Array of Gizmos var dragged_gizmo : Gizmo = null var mouse_pos_on_gizmo_drag := Vector2.ZERO +onready var marching_ants_outline : Sprite = $MarchingAntsOutline + func _ready() -> void: tween = Tween.new() @@ -153,96 +131,44 @@ func move_borders_start() -> void: func move_borders(move : Vector2) -> void: + marching_ants_outline.offset += move self.big_bounding_rectangle.position += move - for polygon in Global.current_project.selections: - polygon.rect_outline.position += move - var borders_copy = polygon.border.duplicate() - for i in borders_copy.size(): - borders_copy[i] += move - - polygon.border = borders_copy func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: + marching_ants_outline.offset = Vector2.ZERO var diff := new_pos - old_pos - var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() - for i in selected_pixels_copy.size(): - selected_pixels_copy[i] += diff + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy, diff) - Global.current_project.selected_pixels = selected_pixels_copy + Global.current_project.selection_bitmap = selected_bitmap_copy commit_undo("Rectangle Select", undo_data) -func select_rect(merge := true) -> void: +func select_rect(rect : Rect2, select := true) -> void: var project : Project = Global.current_project - var polygon : SelectionPolygon = Global.current_project.selections[-1] - var selected_pixels_copy = project.selected_pixels.duplicate() - var polygon_pixels := [] - for x in range(polygon.rect_outline.position.x, polygon.rect_outline.end.x): - for y in range(polygon.rect_outline.position.y, polygon.rect_outline.end.y): - var pos := Vector2(x, y) - polygon_pixels.append(pos) - if pos in selected_pixels_copy or !merge: - continue - selected_pixels_copy.append(pos) - - project.selected_pixels = selected_pixels_copy - if polygon_pixels.size() == 0: - project.selections.erase(polygon) - return - if merge: - merge_selections(polygon) - else: - clip_selections(polygon, polygon_pixels) - project.selections.erase(polygon) - self.big_bounding_rectangle = get_big_bounding_rectangle() + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + var offset_position := Vector2.ZERO + if big_bounding_rectangle.position.x < 0: + rect.position.x -= big_bounding_rectangle.position.x + offset_position.x = big_bounding_rectangle.position.x + if big_bounding_rectangle.position.y < 0: + rect.position.y -= big_bounding_rectangle.position.y + offset_position.y = big_bounding_rectangle.position.y + if offset_position != Vector2.ZERO: + big_bounding_rectangle.position -= offset_position + project.move_bitmap_values(selection_bitmap_copy, -offset_position) -func merge_selections(polygon : SelectionPolygon) -> void: - if Global.current_project.selections.size() < 2: - return - var to_erase := [] - for p in Global.current_project.selections: - if p == polygon: - continue - var arr := Geometry.merge_polygons_2d(polygon.border, p.border) - if arr.size() == 1: # if the selections intersect - polygon.border = arr[0] - polygon.rect_outline = polygon.rect_outline.merge(p.rect_outline) - to_erase.append(p) - - for p in to_erase: - Global.current_project.selections.erase(p) - - -func clip_selections(polygon : SelectionPolygon, polygon_pixels : Array) -> void: - var to_erase := [] - var to_append := [] - for p in Global.current_project.selections: - if p == polygon: - continue - var arr := Geometry.clip_polygons_2d(p.border, polygon.border) - if arr.size() == 0: # if the new selection completely overlaps the current - to_erase.append(p) - else: - p.border = arr[0] - p.rect_outline = get_bounding_rectangle(p.border) - for i in range(1, arr.size()): - var rect = get_bounding_rectangle(arr[i]) - var new_polygon := SelectionPolygon.new(rect) - new_polygon.border = arr[i] - to_append.append(new_polygon) - - var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() - for pixel in polygon_pixels: - selected_pixels_copy.erase(pixel) - Global.current_project.selected_pixels = selected_pixels_copy - - for p in to_erase: - Global.current_project.selections.erase(p) - - for p in to_append: - Global.current_project.selections.append(p) + selection_bitmap_copy.set_bit_rect(rect, select) + big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + + if offset_position != Vector2.ZERO: + big_bounding_rectangle.position += offset_position + project.move_bitmap_values(selection_bitmap_copy, offset_position) + + project.selection_bitmap = selection_bitmap_copy + self.big_bounding_rectangle = big_bounding_rectangle # call getter method func move_content_start() -> void: @@ -254,7 +180,6 @@ func move_content_start() -> void: func move_content(move : Vector2) -> void: move_borders(move) - move_preview_location += move func move_content_confirm() -> void: @@ -263,12 +188,12 @@ func move_content_confirm() -> void: var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) - var selected_pixels_copy = Global.current_project.selected_pixels.duplicate() - for i in selected_pixels_copy.size(): - selected_pixels_copy[i] += move_preview_location - Global.current_project.selected_pixels = selected_pixels_copy + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) + + Global.current_project.selection_bitmap = selected_bitmap_copy preview_image = Image.new() - move_preview_location = Vector2.ZERO + marching_ants_outline.offset = Vector2.ZERO is_moving_content = false commit_undo("Move Selection", undo_data) @@ -276,15 +201,9 @@ func move_content_confirm() -> void: func move_content_cancel() -> void: if preview_image.is_empty(): return - self.big_bounding_rectangle.position -= move_preview_location - for polygon in Global.current_project.selections: - polygon.rect_outline.position -= move_preview_location - var borders_copy = polygon.border.duplicate() - for i in borders_copy.size(): - borders_copy[i] -= move_preview_location - polygon.border = borders_copy - - move_preview_location = Vector2.ZERO + self.big_bounding_rectangle.position -= marching_ants_outline.offset + marching_ants_outline.offset = Vector2.ZERO + is_moving_content = false var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image @@ -299,22 +218,14 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: project.undos += 1 project.undo_redo.create_action(action) - project.undo_redo.add_do_property(project, "selections", redo_data["selections"]) - project.undo_redo.add_do_property(project, "selected_pixels", redo_data["selected_pixels"]) + project.undo_redo.add_do_property(project, "selection_bitmap", redo_data["selection_bitmap"]) project.undo_redo.add_do_property(self, "big_bounding_rectangle", redo_data["big_bounding_rectangle"]) + project.undo_redo.add_do_property(marching_ants_outline, "offset", redo_data["outline_offset"]) - project.undo_redo.add_undo_property(project, "selections", _undo_data["selections"]) - project.undo_redo.add_undo_property(project, "selected_pixels", _undo_data["selected_pixels"]) + project.undo_redo.add_undo_property(project, "selection_bitmap", _undo_data["selection_bitmap"]) project.undo_redo.add_undo_property(self, "big_bounding_rectangle", _undo_data["big_bounding_rectangle"]) + project.undo_redo.add_undo_property(marching_ants_outline, "offset", _undo_data["outline_offset"]) - var i := 0 - for polygon in Global.current_project.selections: - if "border_%s" % i in _undo_data: - project.undo_redo.add_do_property(polygon, "border", redo_data["border_%s" % i]) - project.undo_redo.add_do_property(polygon, "rect_outline", redo_data["rect_outline_%s" % i]) - project.undo_redo.add_undo_property(polygon, "border", _undo_data["border_%s" % i]) - project.undo_redo.add_undo_property(polygon, "rect_outline", _undo_data["rect_outline_%s" % i]) - i += 1 if "image_data" in _undo_data: var image : Image = project.frames[project.current_frame].cels[project.current_layer].image @@ -330,14 +241,9 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: func _get_undo_data(undo_image : bool) -> Dictionary: var data := {} var project := Global.current_project - var i := 0 - data["selections"] = project.selections - data["selected_pixels"] = project.selected_pixels + data["selection_bitmap"] = project.selection_bitmap data["big_bounding_rectangle"] = big_bounding_rectangle - for polygon in Global.current_project.selections: - data["border_%s" % i] = polygon.border - data["rect_outline_%s" % i] = polygon.rect_outline - i += 1 + data["outline_offset"] = marching_ants_outline.offset if undo_image: var image : Image = project.frames[project.current_frame].cels[project.current_layer].image @@ -356,27 +262,21 @@ func cut() -> void: func copy() -> void: var project := Global.current_project - if project.selected_pixels.empty(): + if !project.has_selection(): return var image : Image = project.frames[project.current_frame].cels[project.current_layer].image var to_copy := Image.new() to_copy = image.get_rect(big_bounding_rectangle) - if project.selections.size() > 1 or project.selections[0].border.size() > 4: - to_copy.lock() - # Only remove unincluded pixels if the selection is not a single rectangle - for x in to_copy.get_size().x: - for y in to_copy.get_size().y: - var pos := Vector2(x, y) - if not (pos + big_bounding_rectangle.position) in project.selected_pixels: - to_copy.set_pixelv(pos, Color(0)) - to_copy.unlock() + to_copy.lock() + # Only remove unincluded pixels if the selection is not a single rectangle + for x in to_copy.get_size().x: + for y in to_copy.get_size().y: + var pos := Vector2(x, y) + if not project.selection_bitmap.get_bit(pos + big_bounding_rectangle.position): + to_copy.set_pixelv(pos, Color(0)) + to_copy.unlock() clipboard.image = to_copy - for selection in project.selections: - var selection_duplicate := SelectionPolygon.new(selection.rect_outline) - selection_duplicate.border = selection.border - clipboard.polygons.append(selection_duplicate) - clipboard.position = big_bounding_rectangle.position - clipboard.selected_pixels = project.selected_pixels.duplicate() + clipboard.selection_bitmap = project.selection_bitmap.duplicate() clipboard.big_bounding_rectangle = big_bounding_rectangle @@ -387,21 +287,23 @@ func paste() -> void: var project := Global.current_project var image : Image = project.frames[project.current_frame].cels[project.current_layer].image clear_selection() - project.selections = clipboard.polygons.duplicate() - project.selected_pixels = clipboard.selected_pixels.duplicate() + project.selection_bitmap = clipboard.selection_bitmap.duplicate() self.big_bounding_rectangle = clipboard.big_bounding_rectangle - image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), clipboard.position) + image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) commit_undo("Draw", _undo_data) func delete() -> void: var project := Global.current_project - if project.selected_pixels.empty(): + if !project.has_selection(): return var _undo_data = _get_undo_data(true) var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - for pixel in project.selected_pixels: - image.set_pixelv(pixel, Color(0)) + for x in big_bounding_rectangle.size.x: + for y in big_bounding_rectangle.size.y: + var pos := Vector2(x, y) + big_bounding_rectangle.position + if project.can_pixel_get_drawn(pos): + image.set_pixelv(pos, Color(0)) commit_undo("Draw", _undo_data) @@ -410,24 +312,19 @@ func select_all() -> void: var _undo_data = _get_undo_data(false) clear_selection() var full_rect = Rect2(Vector2.ZERO, project.size) - var new_selection = SelectionPolygon.new(full_rect) - var selections : Array = project.selections.duplicate() - selections.append(new_selection) - project.selections = selections - select_rect() + select_rect(full_rect) commit_undo("Rectangle Select", _undo_data) func clear_selection(use_undo := false) -> void: move_content_confirm() + var project := Global.current_project + var full_rect = Rect2(Vector2.ZERO, project.selection_bitmap.get_size()) var _undo_data = _get_undo_data(false) - var selections : Array = Global.current_project.selections.duplicate() - var selected_pixels : Array = Global.current_project.selected_pixels.duplicate() - selected_pixels.clear() - Global.current_project.selected_pixels = selected_pixels - selections.clear() - Global.current_project.selections = selections + select_rect(full_rect, false) + self.big_bounding_rectangle = Rect2() + marching_ants_outline.offset = Vector2.ZERO if use_undo: commit_undo("Clear Selection", _undo_data) @@ -439,23 +336,6 @@ func _draw() -> void: _position.x = _position.x + Global.current_project.size.x _scale.x = -1 draw_set_transform(_position, rotation, _scale) - for p in Global.current_project.selections: - var points : Array = p.border - for i in range(1, points.size() + 1): - var point0 = points[i - 1] - var point1 - if i >= points.size(): - point1 = points[0] - else: - point1 = points[i] - var start_x = min(point0.x, point1.x) - var start_y = min(point0.y, point1.y) - var end_x = max(point0.x, point1.x) - var end_y = max(point0.y, point1.y) - - var start := Vector2(start_x, start_y) - var end := Vector2(end_x, end_y) - draw_dashed_line(start, end, Color.white, Color.black, 1.0, 1.0, false) if big_bounding_rectangle.size > Vector2.ZERO: for gizmo in gizmos: # Draw gizmos @@ -472,83 +352,6 @@ func _draw() -> void: draw_set_transform(position, rotation, scale) -# Taken and modified from https://github.com/juddrgledhill/godot-dashed-line -func draw_dashed_line(from : Vector2, to : Vector2, color : Color, color2 : Color, width := 1.0, dash_length := 1.0, cap_end := false, antialiased := false) -> void: - var length = (to - from).length() - var normal = (to - from).normalized() - var dash_step = normal * dash_length - - var horizontal : bool = from.y == to.y - var _offset : Vector2 - if horizontal: - _offset = Vector2(line_offset.x, 0) - else: - _offset = Vector2(0, line_offset.y) - - if length < dash_length: # not long enough to dash - draw_line(from, to, color, width, antialiased) - return - - else: - var draw_flag = true - var segment_start = from - var steps = length/dash_length - for _start_length in range(0, steps + 1): - var segment_end = segment_start + dash_step - - var start = segment_start + _offset - start.x = min(start.x, to.x) - start.y = min(start.y, to.y) - - var end = segment_end + _offset - end.x = min(end.x, to.x) - end.y = min(end.y, to.y) - if draw_flag: - draw_line(start, end, color, width, antialiased) - else: - draw_line(start, end, color2, width, antialiased) - if _offset.length() < 1: - draw_line(from, from + _offset, color2, width, antialiased) - else: - var from_offseted : Vector2 = from + _offset - var halfway_point : Vector2 = from_offseted - if horizontal: - halfway_point += Vector2.LEFT - else: - halfway_point += Vector2.UP - - from_offseted.x = min(from_offseted.x, to.x) - from_offseted.y = min(from_offseted.y, to.y) - draw_line(halfway_point, from_offseted, color2, width, antialiased) - draw_line(from, halfway_point, color, width, antialiased) - - segment_start = segment_end - draw_flag = !draw_flag - - if cap_end: - draw_line(segment_start, to, color, width, antialiased) - - -func get_bounding_rectangle(borders : Array) -> Rect2: - var rect := Rect2() - var xmin = borders[0].x - var xmax = borders[0].x - var ymin = borders[0].y - var ymax = borders[0].y - for edge in borders: - if edge.x < xmin: - xmin = edge.x - if edge.x > xmax: - xmax = edge.x - if edge.y < ymin: - ymin = edge.y - if edge.y > ymax: - ymax = edge.y - rect.position = Vector2(xmin, ymin) - rect.end = Vector2(xmax, ymax) - return rect - - func get_preview_image() -> void: var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image @@ -559,7 +362,7 @@ func get_preview_image() -> void: for x in range(0, big_bounding_rectangle.size.x): for y in range(0, big_bounding_rectangle.size.y): var pos := Vector2(x, y) - if not (pos + big_bounding_rectangle.position) in project.selected_pixels: + if not project.selection_bitmap.get_bit(pos + big_bounding_rectangle.position): preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) preview_image.unlock() preview_image_texture = ImageTexture.new() @@ -575,9 +378,6 @@ func get_big_bounding_rectangle() -> Rect2: # Returns a rectangle that contains the entire selection, with multiple polygons var project : Project = Global.current_project var rect := Rect2() - if project.selections.size() == 0: - return rect - rect = project.selections[0].rect_outline - for i in range(1, project.selections.size()): - rect = rect.merge(project.selections[i].rect_outline) + var image : Image = project.bitmap_to_image(project.selection_bitmap) + rect = image.get_used_rect() return rect From 2c8c7db66993cbd7e45617796bf314bd0fe86cac Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Mon, 29 Mar 2021 00:57:00 +0300 Subject: [PATCH 28/69] Selection works with mirror view --- src/UI/Canvas/Selection.gd | 1 - src/UI/TopMenuContainer.gd | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 4fbb614a6129..30c31961bbf6 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -346,7 +346,6 @@ func _draw() -> void: filled_rect.size -= filled_size * 2 draw_rect(filled_rect, Color.white) # Filled white square - if is_moving_content and !preview_image.is_empty(): draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) draw_set_transform(position, rotation, scale) diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 6f30e86db9d0..9b6d67b8527c 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -368,6 +368,11 @@ func window_transparency(value :float) -> void: func toggle_mirror_view() -> void: Global.mirror_view = !Global.mirror_view + Global.canvas.selection.marching_ants_outline.scale.x = -Global.canvas.selection.marching_ants_outline.scale.x + if Global.mirror_view: + Global.canvas.selection.marching_ants_outline.position.x = Global.canvas.selection.marching_ants_outline.position.x + Global.current_project.size.x + else: + Global.canvas.selection.marching_ants_outline.position.x = 0 view_menu.set_item_checked(ViewMenuId.MIRROR_VIEW, Global.mirror_view) From b81a6ca22ce240b746bdd43d4db99ef617c6c53a Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 30 Mar 2021 03:58:20 +0300 Subject: [PATCH 29/69] Draw a black rectangle when the user is making a rectangular selection After they release the mouse, the black rectangle becomes the selection --- src/Tools/RectSelect.gd | 4 ++++ src/UI/Canvas/Selection.gd | 26 ++++++-------------------- src/UI/TopMenuContainer.gd | 1 + 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index f875ce35003a..a10761a756c8 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -36,6 +36,8 @@ func draw_move(position : Vector2) -> void: rect = _start.expand(position).abs() rect = rect.grow_individual(0, 0, 1, 1) _set_cursor_text(rect) + Global.canvas.selection.drawn_rect = rect + Global.canvas.selection.update() func draw_end(position : Vector2) -> void: @@ -49,6 +51,8 @@ func draw_end(position : Vector2) -> void: cursor_text = "" start_position = Vector2.INF rect = Rect2(0, 0, 0, 0) + Global.canvas.selection.drawn_rect = rect + Global.canvas.selection.update() func cursor_move(_position : Vector2) -> void: diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 30c31961bbf6..db1b3a8388fe 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -29,13 +29,12 @@ class Gizmo: var clipboard := Clipboard.new() -var tween : Tween -var line_offset := Vector2.ZERO setget _offset_changed var is_moving_content := false var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed var preview_image := Image.new() var preview_image_texture : ImageTexture var undo_data : Dictionary +var drawn_rect := Rect2(0, 0, 0, 0) var gizmos := [] # Array of Gizmos var dragged_gizmo : Gizmo = null var mouse_pos_on_gizmo_drag := Vector2.ZERO @@ -44,12 +43,6 @@ onready var marching_ants_outline : Sprite = $MarchingAntsOutline func _ready() -> void: - tween = Tween.new() - tween.connect("tween_completed", self, "_offset_tween_completed") - add_child(tween) - tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) - tween.start() - gizmos.append(Gizmo.new(Vector2(-1, -1))) # Top left gizmos.append(Gizmo.new(Vector2(0, -1))) # Center top gizmos.append(Gizmo.new(Vector2(1, -1))) # Top right @@ -99,17 +92,6 @@ func _input(event : InputEvent) -> void: dragged_gizmo = null -func _offset_tween_completed(_object, _key) -> void: - self.line_offset = Vector2.ZERO - tween.interpolate_property(self, "line_offset", Vector2.ZERO, Vector2(2, 2), 1) - tween.start() - - -func _offset_changed(value : Vector2) -> void: - line_offset = value - update() - - func _big_bounding_rectangle_changed(value : Rect2) -> void: big_bounding_rectangle = value var rect_pos : Vector2 = big_bounding_rectangle.position @@ -133,6 +115,7 @@ func move_borders_start() -> void: func move_borders(move : Vector2) -> void: marching_ants_outline.offset += move self.big_bounding_rectangle.position += move + update() func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: @@ -143,6 +126,7 @@ func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: Global.current_project.selection_bitmap = selected_bitmap_copy commit_undo("Rectangle Select", undo_data) + update() func select_rect(rect : Rect2, select := true) -> void: @@ -196,6 +180,7 @@ func move_content_confirm() -> void: marching_ants_outline.offset = Vector2.ZERO is_moving_content = false commit_undo("Move Selection", undo_data) + update() func move_content_cancel() -> void: @@ -210,6 +195,7 @@ func move_content_cancel() -> void: cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) preview_image = Image.new() + update() func commit_undo(action : String, _undo_data : Dictionary) -> void: @@ -336,7 +322,7 @@ func _draw() -> void: _position.x = _position.x + Global.current_project.size.x _scale.x = -1 draw_set_transform(_position, rotation, _scale) - + draw_rect(drawn_rect, Color.black, false) if big_bounding_rectangle.size > Vector2.ZERO: for gizmo in gizmos: # Draw gizmos draw_rect(gizmo.rect, Color.black) diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 9b6d67b8527c..639cfa737b61 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -373,6 +373,7 @@ func toggle_mirror_view() -> void: Global.canvas.selection.marching_ants_outline.position.x = Global.canvas.selection.marching_ants_outline.position.x + Global.current_project.size.x else: Global.canvas.selection.marching_ants_outline.position.x = 0 + Global.canvas.selection.update() view_menu.set_item_checked(ViewMenuId.MIRROR_VIEW, Global.mirror_view) From a83f4263e9c54e2f7351a734ebad9cb6be4382c6 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Wed, 31 Mar 2021 23:44:06 +0300 Subject: [PATCH 30/69] Make Selection.gd update when undoing/redoing --- src/Autoload/Global.gd | 18 ++++++++++-------- src/UI/Canvas/Selection.gd | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 50fbed99f5b7..fbfb781b7ea9 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -195,7 +195,7 @@ func _ready() -> void: # XDGDataDirs depends on it nyaa directory_module = XDGDataPaths.new() image_clipboard = Image.new() - Input.set_custom_mouse_cursor(Global.cursor_image, Input.CURSOR_CROSS, Vector2(15, 15)) + Input.set_custom_mouse_cursor(cursor_image, Input.CURSOR_CROSS, Vector2(15, 15)) var root = get_tree().get_root() control = find_node_by_name(root, "Control") @@ -358,11 +358,12 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr for j in project.layers.size(): canvas.update_texture(j, i, project) + canvas.selection.update() if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.update() - Global.canvas.pixel_grid.update() - Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] + canvas.grid.update() + canvas.pixel_grid.update() + 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 @@ -392,11 +393,12 @@ func redo(_frame_index := -1, _layer_index := -1, project : Project = current_pr for j in project.layers.size(): canvas.update_texture(j, i, project) + canvas.selection.update() if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.update() - Global.canvas.pixel_grid.update() - Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] + canvas.grid.update() + canvas.pixel_grid.update() + cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] elif "Frame" in action_name: if project.frames.size() == 1: # Stop animating @@ -613,7 +615,7 @@ func save_project_to_recent_list(path : String) -> void: func update_recent_projects_submenu() -> void: - for project in Global.recent_projects: + for project in recent_projects: recent_projects_submenu.add_item(project.get_file()) func use_osx_shortcuts() -> void: diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index db1b3a8388fe..000894177d42 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -311,6 +311,7 @@ func clear_selection(use_undo := false) -> void: self.big_bounding_rectangle = Rect2() marching_ants_outline.offset = Vector2.ZERO + update() if use_undo: commit_undo("Clear Selection", _undo_data) From 33b5f9d5db9b80668d0b2894450137be6cc62824 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:10:42 +0300 Subject: [PATCH 31/69] Fix brushes not working properly with non-rectangular selections --- src/Tools/Draw.gd | 48 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index f564674dcb50..e42d35d42575 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -100,7 +100,6 @@ func update_brush() -> void: else: var random = randi() % _brush.random.size() _brush_image = _create_blended_brush_image(_brush.random[random]) - _brush_image.lock() _brush_texture.create_from_image(_brush_image, 0) update_mirror_brush() _indicator = _create_brush_indicator() @@ -114,7 +113,6 @@ func update_random_image() -> void: return var random = randi() % _brush.random.size() _brush_image = _create_blended_brush_image(_brush.random[random]) - _brush_image.lock() _brush_texture.create_from_image(_brush_image, 0) _indicator = _create_brush_indicator() update_mirror_brush() @@ -273,23 +271,43 @@ func draw_tool_brush(position : Vector2) -> void: return var src_rect := Rect2(dst_rect.position - dst, dst_rect.size) dst = dst_rect.position - - _draw_brush_image(_brush_image, src_rect, dst) + var brush_image : Image = remove_unselected_parts_of_brush(_brush_image, dst) + _draw_brush_image(brush_image, src_rect, dst) # Handle Mirroring var mirror_x = (project.x_symmetry_point + 1) - dst.x - src_rect.size.x var mirror_y = (project.y_symmetry_point + 1) - dst.y - src_rect.size.y - var mirror_x_inside : bool - var mirror_y_inside : bool - mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, dst.y)) - mirror_y_inside = project.can_pixel_get_drawn(Vector2(dst.x, mirror_y)) - - if tool_slot.horizontal_mirror and mirror_x_inside: - _draw_brush_image(_mirror_brushes.x, _flip_rect(src_rect, size, true, false), Vector2(mirror_x, dst.y)) - if tool_slot.vertical_mirror and mirror_y_inside: - _draw_brush_image(_mirror_brushes.xy, _flip_rect(src_rect, size, true, true), Vector2(mirror_x, mirror_y)) - if tool_slot.vertical_mirror and mirror_y_inside: - _draw_brush_image(_mirror_brushes.y, _flip_rect(src_rect, size, false, true), Vector2(dst.x, mirror_y)) + + if tool_slot.horizontal_mirror: + var x_dst := Vector2(mirror_x, dst.y) + var mirror_brush_x : Image = remove_unselected_parts_of_brush(_mirror_brushes.x, x_dst) + _draw_brush_image(mirror_brush_x, _flip_rect(src_rect, size, true, false), x_dst) + if tool_slot.vertical_mirror: + var xy_dst := Vector2(mirror_x, mirror_y) + var mirror_brush_xy : Image = remove_unselected_parts_of_brush(_mirror_brushes.xy, xy_dst) + _draw_brush_image(mirror_brush_xy, _flip_rect(src_rect, size, true, true), xy_dst) + if tool_slot.vertical_mirror: + var y_dst := Vector2(dst.x, mirror_y) + var mirror_brush_y : Image = remove_unselected_parts_of_brush(_mirror_brushes.y, y_dst) + _draw_brush_image(mirror_brush_y, _flip_rect(src_rect, size, false, true), y_dst) + + +func remove_unselected_parts_of_brush(brush : Image, dst : Vector2) -> Image: + var project : Project = Global.current_project + var size := brush.get_size() + var new_brush := Image.new() + new_brush.copy_from(_mirror_brushes.x) + if !project.has_selection(): + return new_brush + + new_brush.lock() + for x in size.x: + for y in size.y: + var pos := Vector2(x, y) + dst + if !project.selection_bitmap.get_bit(pos): + new_brush.set_pixel(x, y, Color(0)) + new_brush.unlock() + return new_brush func draw_indicator() -> void: From 21003b44c6a7b4bcc6e359af80bbef0442699106 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 1 Apr 2021 02:00:18 +0300 Subject: [PATCH 32/69] Added invert selection --- project.godot | 5 +++++ src/Classes/Project.gd | 8 ++++++++ src/UI/Canvas/Selection.gd | 10 ++++++++++ src/UI/TopMenuContainer.gd | 5 ++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/project.godot b/project.godot index 90caf308044b..e3d60239b5d4 100644 --- a/project.godot +++ b/project.godot @@ -503,6 +503,11 @@ select_all={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null) ] } +invert_selection={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":73,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 445a601746ee..fe8f4fc05e4c 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -609,6 +609,14 @@ func can_pixel_get_drawn(pixel : Vector2) -> bool: else: return true + +func invert_bitmap(bitmap : BitMap) -> void: + for x in bitmap.get_size().x: + for y in bitmap.get_size().y: + var pos := Vector2(x, y) + bitmap.set_bit(pos, !bitmap.get_bit(pos)) + + # Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L605 func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> void: if new_size == bitmap.get_size(): diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 000894177d42..e6f1b0880a52 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -302,6 +302,16 @@ func select_all() -> void: commit_undo("Rectangle Select", _undo_data) +func invert() -> void: + var project := Global.current_project + var _undo_data = _get_undo_data(false) + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + project.invert_bitmap(selection_bitmap_copy) + project.selection_bitmap = selection_bitmap_copy + self.big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + commit_undo("Rectangle Select", _undo_data) + + func clear_selection(use_undo := false) -> void: move_content_confirm() var project := Global.current_project diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 639cfa737b61..69b76353da13 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -5,7 +5,7 @@ enum FileMenuId {NEW, OPEN, OPEN_LAST_PROJECT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, PREFERENCES} enum ViewMenuId {TILE_MODE, WINDOW_TRANSPARENCY, PANEL_LAYOUT, MIRROR_VIEW, SHOW_GRID, SHOW_PIXEL_GRID, SHOW_RULERS, SHOW_GUIDES, SHOW_ANIMATION_TIMELINE, ZEN_MODE, FULLSCREEN_MODE} enum ImageMenuId {SCALE_IMAGE, CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} -enum SelectMenuId {SELECT_ALL, CLEAR_SELECTION} +enum SelectMenuId {SELECT_ALL, CLEAR_SELECTION, INVERT} enum HelpMenuId {VIEW_SPLASH_SCREEN, ONLINE_DOCS, ISSUE_TRACKER, CHANGELOG, ABOUT_PIXELORAMA} var file_menu_button : MenuButton @@ -178,6 +178,7 @@ func setup_select_menu() -> void: var select_menu_items := { # order as in EditMenuId enum "Select All" : InputMap.get_action_list("select_all")[0].get_scancode_with_modifiers(), "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), + "Invert" : InputMap.get_action_list("invert_selection")[0].get_scancode_with_modifiers(), } var select_menu : PopupMenu = select_menu_button.get_popup() var i := 0 @@ -513,6 +514,8 @@ func select_menu_id_pressed(id : int) -> void: Global.canvas.selection.select_all() SelectMenuId.CLEAR_SELECTION: Global.canvas.selection.clear_selection(true) + SelectMenuId.INVERT: + Global.canvas.selection.invert() func help_menu_id_pressed(id : int) -> void: From 08c346756c76dc3cdac06314784d824217721773 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 1 Apr 2021 03:36:31 +0300 Subject: [PATCH 33/69] Cache has_selection as a variable for a speedup --- src/Classes/Project.gd | 10 ++++------ src/Tools/BaseTool.gd | 2 +- src/Tools/Bucket.gd | 4 ++-- src/Tools/Draw.gd | 2 +- src/Tools/Move.gd | 6 +++--- src/UI/Canvas/Selection.gd | 4 ++-- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index fe8f4fc05e4c..604437186cac 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -23,6 +23,7 @@ var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide var selection_bitmap := BitMap.new() setget _selection_bitmap_changed +var has_selection := false var selected_pixels := [] # For every camera (currently there are 3) @@ -93,6 +94,7 @@ func _selection_bitmap_changed(value : BitMap) -> void: var image_texture := ImageTexture.new() image_texture.create_from_image(image, 0) Global.canvas.selection.marching_ants_outline.texture = image_texture + has_selection = !image.is_invisible() func change_project() -> void: @@ -592,10 +594,6 @@ func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 -func has_selection(bitmap : BitMap = selection_bitmap) -> bool: - return bitmap.get_true_bit_count() > 0 - - func can_pixel_get_drawn(pixel : Vector2) -> bool: var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position if selection_position.x < 0: @@ -604,7 +602,7 @@ func can_pixel_get_drawn(pixel : Vector2) -> bool: pixel.y -= selection_position.y if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: return false - if has_selection(): + if has_selection: return selection_bitmap.get_bit(pixel) else: return true @@ -650,7 +648,7 @@ func bitmap_to_image(bitmap : BitMap) -> Image: func get_selection_rectangle(bitmap : BitMap = selection_bitmap) -> Rect2: var rect := Rect2(Vector2.ZERO, Vector2.ZERO) - if has_selection(bitmap): + if bitmap.get_true_bit_count() > 0: var image : Image = bitmap_to_image(bitmap) rect = image.get_used_rect() return rect diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd index 379dcdc05480..ae832e098a0f 100644 --- a/src/Tools/BaseTool.gd +++ b/src/Tools/BaseTool.gd @@ -81,7 +81,7 @@ func draw_preview() -> void: func _get_draw_rect() -> Rect2: - if Global.current_project.has_selection(): + if Global.current_project.has_selection: return Global.current_project.get_selection_rectangle() else: return Global.current_project.tile_mode_rects[Global.TileMode.NONE] diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index 4e330b91651e..c923e6d1ddff 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -97,7 +97,7 @@ func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_confirm() if Global.current_project.layers[Global.current_project.current_layer].locked or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position): return - if Global.current_project.has_selection() and not Global.current_project.selection_bitmap.get_bit(position): + if Global.current_project.has_selection and not Global.current_project.selection_bitmap.get_bit(position): return var undo_data = _get_undo_data() if _fill_area == 0: @@ -128,7 +128,7 @@ func fill_in_color(position : Vector2) -> void: for x in Global.current_project.size.x: for y in Global.current_project.size.y: var pos := Vector2(x, y) - if project.has_selection() and not project.selection_bitmap.get_bit(pos): + if project.has_selection and not project.selection_bitmap.get_bit(pos): continue if image.get_pixelv(pos).is_equal_approx(color): _set_pixel(image, x, y, tool_slot.color) diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index e42d35d42575..4a8919a38edc 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -297,7 +297,7 @@ func remove_unselected_parts_of_brush(brush : Image, dst : Vector2) -> Image: var size := brush.get_size() var new_brush := Image.new() new_brush.copy_from(_mirror_brushes.x) - if !project.has_selection(): + if !project.has_selection: return new_brush new_brush.lock() diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index 3b400243b9cd..b3048f6dfbc4 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -8,12 +8,12 @@ var offset : Vector2 func draw_start(position : Vector2) -> void: starting_pos = position offset = position - if Global.current_project.has_selection(): + if Global.current_project.has_selection: Global.canvas.selection.move_content_start() func draw_move(position : Vector2) -> void: - if Global.current_project.has_selection(): + if Global.current_project.has_selection: Global.canvas.selection.move_content(position - offset) offset = position else: @@ -28,7 +28,7 @@ func draw_end(position : Vector2) -> void: var project : Project = Global.current_project var image : Image = _get_draw_image() - if project.has_selection(): + if project.has_selection: pass # Global.canvas.selection.move_content_end() # Global.canvas.selection.move_borders_end(position, starting_pos) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index e6f1b0880a52..fbdd7e22fea7 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -248,7 +248,7 @@ func cut() -> void: func copy() -> void: var project := Global.current_project - if !project.has_selection(): + if !project.has_selection: return var image : Image = project.frames[project.current_frame].cels[project.current_layer].image var to_copy := Image.new() @@ -281,7 +281,7 @@ func paste() -> void: func delete() -> void: var project := Global.current_project - if !project.has_selection(): + if !project.has_selection: return var _undo_data = _get_undo_data(true) var image : Image = project.frames[project.current_frame].cels[project.current_layer].image From 839a652a150bef7f5deb84b15c0f0710cabfb633 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 1 Apr 2021 03:57:13 +0300 Subject: [PATCH 34/69] Fix conflict issues with the shape tools --- src/UI/Canvas/Canvas.tscn | 4 ++-- src/UI/UI.tscn | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/UI/Canvas/Canvas.tscn b/src/UI/Canvas/Canvas.tscn index 9d7142fb56ee..c23c9d8c23a6 100644 --- a/src/UI/Canvas/Canvas.tscn +++ b/src/UI/Canvas/Canvas.tscn @@ -14,7 +14,7 @@ blend_mode = 4 [sub_resource type="ShaderMaterial" id=2] -shader = ExtResource( 8 ) +shader = ExtResource( 9 ) shader_param/first_color = Color( 1, 1, 1, 1 ) shader_param/second_color = Color( 0, 0, 0, 1 ) shader_param/animated = true @@ -48,7 +48,7 @@ script = ExtResource( 6 ) script = ExtResource( 2 ) [node name="Selection" type="Node2D" parent="."] -script = ExtResource( 7 ) +script = ExtResource( 8 ) [node name="MarchingAntsOutline" type="Sprite" parent="Selection"] material = SubResource( 2 ) diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 6da5a8d9c293..5b177614c834 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=30 format=2] +[gd_scene load_steps=29 format=2] [ext_resource path="res://src/UI/ToolButtons.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/CanvasPreview.tscn" type="PackedScene" id=2] @@ -88,7 +88,7 @@ __meta__ = { margin_left = 7.0 margin_top = 7.0 margin_right = 39.0 -margin_bottom = 363.0 +margin_bottom = 399.0 size_flags_horizontal = 4 size_flags_vertical = 0 script = ExtResource( 1 ) @@ -126,7 +126,7 @@ button_mask = 3 [node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Move"] margin_right = 32.0 margin_bottom = 32.0 -texture = ExtResource( 24 ) +texture = ExtResource( 26 ) __meta__ = { "_edit_use_anchors_": false } @@ -260,9 +260,9 @@ __meta__ = { [node name="RectangleTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 288.0 +margin_top = 324.0 margin_right = 32.0 -margin_bottom = 320.0 +margin_bottom = 356.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -278,9 +278,9 @@ __meta__ = { [node name="EllipseTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 324.0 +margin_top = 360.0 margin_right = 32.0 -margin_bottom = 356.0 +margin_bottom = 392.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 From 33043f9c6b22a531d352d6b4d77409281ef673ac Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 2 Apr 2021 02:56:02 +0300 Subject: [PATCH 35/69] Made the bitmap image squared so the marching ants effect will be the same on both dimensions There may be a better way to fix the issue, perhaps inside the shader itself. --- src/Classes/Project.gd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 604437186cac..eb32113a9cb0 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -635,7 +635,8 @@ func bitmap_to_image(bitmap : BitMap) -> Image: var image := Image.new() var width := bitmap.get_size().x var height := bitmap.get_size().y - image.create(width, height, false, Image.FORMAT_LA8) + var square_size = max(width, height) + image.create(square_size, square_size, false, Image.FORMAT_LA8) image.lock() for x in width: for y in height: From f956e8bf3d8a2515bf2e584fbbbaac6e92575ba8 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 2 Apr 2021 14:54:19 +0300 Subject: [PATCH 36/69] Some optimizations to call selection_bitmap_changed() less times --- src/Classes/Project.gd | 13 +++++++------ src/Tools/RectSelect.gd | 11 +++++++---- src/UI/Canvas/Selection.gd | 6 +++++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index eb32113a9cb0..860e22f08212 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -22,7 +22,7 @@ var y_symmetry_point var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide -var selection_bitmap := BitMap.new() setget _selection_bitmap_changed +var selection_bitmap := BitMap.new() var has_selection := false var selected_pixels := [] @@ -88,13 +88,14 @@ func commit_redo() -> void: Global.control.redone = false -func _selection_bitmap_changed(value : BitMap) -> void: - selection_bitmap = value - var image : Image = bitmap_to_image(selection_bitmap) +func selection_bitmap_changed() -> void: + var image := Image.new() + has_selection = selection_bitmap.get_true_bit_count() > 0 + if has_selection: + image = bitmap_to_image(selection_bitmap) var image_texture := ImageTexture.new() image_texture.create_from_image(image, 0) Global.canvas.selection.marching_ants_outline.texture = image_texture - has_selection = !image.is_invisible() func change_project() -> void: @@ -216,7 +217,7 @@ func change_project() -> void: for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) - self.selection_bitmap = selection_bitmap # call getter method + selection_bitmap_changed() Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index a10761a756c8..7b6bc2154ff6 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -15,8 +15,6 @@ func draw_start(position : Vector2) -> void: undo_data = Global.canvas.selection._get_undo_data(false) if !Global.canvas.selection.big_bounding_rectangle.has_point(position): - if !Tools.shift and !Tools.control: - Global.canvas.selection.clear_selection() _start = Rect2(position, Vector2.ZERO) else: @@ -44,8 +42,13 @@ func draw_end(position : Vector2) -> void: if _move: Global.canvas.selection.move_borders_end(position, start_position) else: - Global.canvas.selection.select_rect(rect, !Tools.control) - Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + if !Tools.shift and !Tools.control: + Global.canvas.selection.clear_selection() + if rect.size == Vector2.ZERO and Global.current_project.has_selection: + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + if rect.size != Vector2.ZERO: + Global.canvas.selection.select_rect(rect, !Tools.control) + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) _move = false cursor_text = "" diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index fbdd7e22fea7..e9204a63dc97 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -218,7 +218,9 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) project.undo_redo.add_undo_property(image, "data", _undo_data["image_data"]) project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) + project.undo_redo.add_do_method(project, "selection_bitmap_changed") project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) + project.undo_redo.add_undo_method(project, "selection_bitmap_changed") project.undo_redo.commit_action() undo_data.clear() @@ -313,8 +315,10 @@ func invert() -> void: func clear_selection(use_undo := false) -> void: - move_content_confirm() var project := Global.current_project + if !project.has_selection: + return + move_content_confirm() var full_rect = Rect2(Vector2.ZERO, project.selection_bitmap.get_size()) var _undo_data = _get_undo_data(false) select_rect(full_rect, false) From 0882bf75bb343efb0723cb322353def4d1567b1b Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 04:03:36 +0300 Subject: [PATCH 37/69] Restored almost all of the image effects Left to do: - Change gradient's behavior. Unsure of how it will work with non-rectangular selections yet, but it should not generate pixels outside of the selection. - Restore rotation - Resize bitmap on image resize - Remove the `pixels` array from the ImageEffect --- src/Autoload/DrawingAlgos.gd | 338 +++++++++--------- src/Classes/ImageEffect.gd | 1 + src/Classes/Project.gd | 4 +- .../Dialogs/ImageEffects/DesaturateDialog.gd | 2 +- .../Dialogs/ImageEffects/FlipImageDialog.gd | 23 +- src/UI/Dialogs/ImageEffects/GradientDialog.gd | 2 +- src/UI/Dialogs/ImageEffects/HSVDialog.gd | 2 +- .../ImageEffects/InvertColorsDialog.gd | 2 +- src/UI/Dialogs/ImageEffects/OutlineDialog.gd | 2 +- 9 files changed, 196 insertions(+), 180 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index d85c610d2654..254f750f8a84 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -369,40 +369,48 @@ func general_undo_centralize() -> void: Global.current_project.undo_redo.commit_action() -func invert_image_colors(image : Image, pixels : Array, red := true, green := true, blue := true, alpha := false) -> void: +func invert_image_colors(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: image.lock() - for i in pixels: - var px_color := image.get_pixelv(i) - # Manually invert each color channel - if red: - px_color.r = 1.0 - px_color.r - if green: - px_color.g = 1.0 - px_color.g - if blue: - px_color.b = 1.0 - px_color.b - if alpha: - px_color.a = 1.0 - px_color.a - image.set_pixelv(i, px_color) - - -func desaturate_image(image : Image, pixels : Array, red := true, green := true, blue := true, alpha := false) -> void: + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var px_color := image.get_pixelv(pos) + # Manually invert each color channel + if red: + px_color.r = 1.0 - px_color.r + if green: + px_color.g = 1.0 - px_color.g + if blue: + px_color.b = 1.0 - px_color.b + if alpha: + px_color.a = 1.0 - px_color.a + image.set_pixelv(pos, px_color) + + +func desaturate_image(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: image.lock() - for i in pixels: - var px_color := image.get_pixelv(i) - var gray = px_color.v - if red: - px_color.r = gray - if green: - px_color.g = gray - if blue: - px_color.b = gray - if alpha: - px_color.a = gray - - image.set_pixelv(i, px_color) - - -func generate_outline(image : Image, pixels : Array, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var px_color := image.get_pixelv(pos) + var gray = px_color.v + if red: + px_color.r = gray + if green: + px_color.g = gray + if blue: + px_color.b = gray + if alpha: + px_color.a = gray + + image.set_pixelv(pos, px_color) + + +func generate_outline(image : Image, affect_selection : bool, project : Project, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: if image.is_invisible(): return var new_image := Image.new() @@ -410,169 +418,175 @@ func generate_outline(image : Image, pixels : Array, outline_color : Color, thic new_image.lock() image.lock() - for pos in pixels: - var current_pixel := image.get_pixelv(pos) - if current_pixel.a == 0: - continue - - for i in range(1, thickness + 1): - if inside_image: - var outline_pos : Vector2 = pos + Vector2.LEFT # Left - if outline_pos.x < 0 || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.RIGHT * (i - 1) - if new_pos.x < Global.current_project.size.x: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) - - outline_pos = pos + Vector2.RIGHT # Right - if outline_pos.x >= Global.current_project.size.x || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.LEFT * (i - 1) - if new_pos.x >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) - - outline_pos = pos + Vector2.UP # Up - if outline_pos.y < 0 || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.DOWN * (i - 1) - if new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) - - outline_pos = pos + Vector2.DOWN # Down - if outline_pos.y >= Global.current_project.size.y || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.UP * (i - 1) - if new_pos.y >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) - - if diagonal: - outline_pos = pos + (Vector2.LEFT + Vector2.UP) # Top left - if (outline_pos.x < 0 && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.DOWN) * (i - 1) - if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + var current_pixel := image.get_pixelv(pos) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + if current_pixel.a == 0: + continue + + for i in range(1, thickness + 1): + if inside_image: + var outline_pos : Vector2 = pos + Vector2.LEFT # Left + if outline_pos.x < 0 || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.RIGHT * (i - 1) + if new_pos.x < Global.current_project.size.x: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a > 0: new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + (Vector2.LEFT + Vector2.DOWN) # Bottom left - if (outline_pos.x < 0 && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.UP) * (i - 1) - if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: + outline_pos = pos + Vector2.RIGHT # Right + if outline_pos.x >= Global.current_project.size.x || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.LEFT * (i - 1) + if new_pos.x >= 0: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a > 0: new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + (Vector2.RIGHT + Vector2.UP) # Top right - if (outline_pos.x >= Global.current_project.size.x && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.DOWN) * (i - 1) - if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: + outline_pos = pos + Vector2.UP # Up + if outline_pos.y < 0 || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.DOWN * (i - 1) + if new_pos.y < Global.current_project.size.y: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a > 0: new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + (Vector2.RIGHT + Vector2.DOWN) # Bottom right - if (outline_pos.x >= Global.current_project.size.x && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.UP) * (i - 1) - if new_pos.x >= 0 && new_pos.y >= 0: + outline_pos = pos + Vector2.DOWN # Down + if outline_pos.y >= Global.current_project.size.y || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + Vector2.UP * (i - 1) + if new_pos.y >= 0: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a > 0: new_image.set_pixelv(new_pos, outline_color) - else: - var new_pos : Vector2 = pos + Vector2.LEFT * i # Left - if new_pos.x >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + Vector2.RIGHT * i # Right - if new_pos.x < Global.current_project.size.x: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + Vector2.UP * i # Up - if new_pos.y >= 0: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - new_pos = pos + Vector2.DOWN * i # Down - if new_pos.y < Global.current_project.size.y: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a == 0: - new_image.set_pixelv(new_pos, outline_color) - - if diagonal: - new_pos = pos + (Vector2.LEFT + Vector2.UP) * i # Top left - if new_pos.x >= 0 && new_pos.y >= 0: + if diagonal: + outline_pos = pos + (Vector2.LEFT + Vector2.UP) # Top left + if (outline_pos.x < 0 && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.DOWN) * (i - 1) + if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + (Vector2.LEFT + Vector2.DOWN) # Bottom left + if (outline_pos.x < 0 && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.UP) * (i - 1) + if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + (Vector2.RIGHT + Vector2.UP) # Top right + if (outline_pos.x >= Global.current_project.size.x && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.DOWN) * (i - 1) + if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + outline_pos = pos + (Vector2.RIGHT + Vector2.DOWN) # Bottom right + if (outline_pos.x >= Global.current_project.size.x && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: + var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.UP) * (i - 1) + if new_pos.x >= 0 && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a > 0: + new_image.set_pixelv(new_pos, outline_color) + + else: + var new_pos : Vector2 = pos + Vector2.LEFT * i # Left + if new_pos.x >= 0: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - new_pos = pos + (Vector2.LEFT + Vector2.DOWN) * i # Bottom left - if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: + new_pos = pos + Vector2.RIGHT * i # Right + if new_pos.x < Global.current_project.size.x: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - new_pos = pos + (Vector2.RIGHT + Vector2.UP) * i # Top right - if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: + new_pos = pos + Vector2.UP * i # Up + if new_pos.y >= 0: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - new_pos = pos + (Vector2.RIGHT + Vector2.DOWN) * i # Bottom right - if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + new_pos = pos + Vector2.DOWN * i # Down + if new_pos.y < Global.current_project.size.y: var new_pixel = image.get_pixelv(new_pos) if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) + if diagonal: + new_pos = pos + (Vector2.LEFT + Vector2.UP) * i # Top left + if new_pos.x >= 0 && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) + + new_pos = pos + (Vector2.LEFT + Vector2.DOWN) * i # Bottom left + if new_pos.x >= 0 && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) + + new_pos = pos + (Vector2.RIGHT + Vector2.UP) * i # Top right + if new_pos.x < Global.current_project.size.x && new_pos.y >= 0: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) + + new_pos = pos + (Vector2.RIGHT + Vector2.DOWN) * i # Bottom right + if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) + image.unlock() new_image.unlock() image.copy_from(new_image) -func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, pixels : Array) -> void: +func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, affect_selection : bool, project : Project) -> void: img.lock() - for i in pixels: - var c : Color = img.get_pixelv(i) - # Hue - var hue = range_lerp(c.h,0,1,-180,180) - hue = hue + delta_h - - while(hue >= 180): - hue -= 360 - while(hue < -180): - hue += 360 - - # Saturation - var sat = c.s - if delta_s > 0: - sat = range_lerp(delta_s,0,100,c.s,1) - elif delta_s < 0: - sat = range_lerp(delta_s,-100,0,0,c.s) - - # Value - var val = c.v - if delta_v > 0: - val = range_lerp(delta_v,0,100,c.v,1) - elif delta_v < 0: - val = range_lerp(delta_v,-100,0,0,c.v) - - c.h = range_lerp(hue,-180,180,0,1) - c.s = sat - c.v = val - img.set_pixelv(i,c) - - img.unlock() - - -func generate_gradient(image : Image, colors : Array, steps := 2, direction : int = GradientDirection.TOP, pixels = Global.current_project.selected_pixels) -> void: + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var c : Color = img.get_pixelv(pos) + # Hue + var hue = range_lerp(c.h,0,1,-180,180) + hue = hue + delta_h + + while(hue >= 180): + hue -= 360 + while(hue < -180): + hue += 360 + + # Saturation + var sat = c.s + if delta_s > 0: + sat = range_lerp(delta_s,0,100,c.s,1) + elif delta_s < 0: + sat = range_lerp(delta_s,-100,0,0,c.s) + + # Value + var val = c.v + if delta_v > 0: + val = range_lerp(delta_v,0,100,c.v,1) + elif delta_v < 0: + val = range_lerp(delta_v,-100,0,0,c.v) + + c.h = range_lerp(hue,-180,180,0,1) + c.s = sat + c.v = val + img.set_pixelv(pos, c) + + +func generate_gradient(image : Image, colors : Array, steps : int, direction : int, affect_selection : bool, project : Project) -> void: if colors.size() < 2: return @@ -586,7 +600,11 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in if direction == GradientDirection.BOTTOM or direction == GradientDirection.RIGHT: colors.invert() - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) + var selection_rectangle := Rect2() + if affect_selection and project.has_selection: + selection_rectangle = project.get_selection_rectangle() + else: + selection_rectangle = Rect2(Vector2.ZERO, project.size) var size := selection_rectangle.size image.lock() var gradient_size @@ -598,7 +616,7 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in var start = i * gradient_size var end = (i + 1) * gradient_size for yy in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + pixels[0] + var pos : Vector2 = Vector2(xx, yy) + selection_rectangle.position image.set_pixelv(pos, colors[i]) else: @@ -608,5 +626,5 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in var start = i * gradient_size var end = (i + 1) * gradient_size for xx in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + pixels[0] + var pos : Vector2 = Vector2(xx, yy) + selection_rectangle.position image.set_pixelv(pos, colors[i]) diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index eba49cef763a..1b6b61eeb249 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -113,6 +113,7 @@ func update_preview() -> void: _: preview_image.copy_from(current_frame) commit_action(preview_image, pixels) + preview_image.unlock() preview_texture.create_from_image(preview_image, 0) preview.texture = preview_texture diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 860e22f08212..2fcad8763229 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -90,11 +90,11 @@ func commit_redo() -> void: func selection_bitmap_changed() -> void: var image := Image.new() + var image_texture := ImageTexture.new() has_selection = selection_bitmap.get_true_bit_count() > 0 if has_selection: image = bitmap_to_image(selection_bitmap) - var image_texture := ImageTexture.new() - image_texture.create_from_image(image, 0) + image_texture.create_from_image(image, 0) Global.canvas.selection.marching_ants_outline.texture = image_texture diff --git a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd index 3fc7020013f8..43a665a684f0 100644 --- a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd +++ b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd @@ -14,7 +14,7 @@ func set_nodes() -> void: func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.desaturate_image(_cel, _pixels, red, green, blue, alpha) + DrawingAlgos.desaturate_image(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd index a49260acd690..3124c682f8f9 100644 --- a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd +++ b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd @@ -12,7 +12,7 @@ func set_nodes() -> void: func commit_action(_cel : Image, _pixels : Array, project : Project = Global.current_project) -> void: - flip_image(_cel, _pixels, project) + flip_image(_cel, selection_checkbox.pressed, project) func _on_FlipHorizontal_toggled(_button_pressed : bool) -> void: @@ -23,14 +23,8 @@ func _on_FlipVertical_toggled(_button_pressed : bool) -> void: update_preview() -func _on_SelectionCheckBox_toggled(button_pressed : bool) -> void: - ._on_SelectionCheckBox_toggled(button_pressed) - update_preview() - - -func flip_image(image : Image, _pixels : Array, project : Project = Global.current_project) -> void: - var entire_image_selected : bool = _pixels.size() == project.size.x * project.size.y - if entire_image_selected: +func flip_image(image : Image, affect_selection : bool, project : Project = Global.current_project) -> void: + if !(affect_selection and project.has_selection): if flip_h.pressed: image.flip_x() if flip_v.pressed: @@ -41,10 +35,13 @@ func flip_image(image : Image, _pixels : Array, project : Project = Global.curre selected_image.create(image.get_width(), image.get_height(), false, Image.FORMAT_RGBA8) selected_image.lock() image.lock() - for i in _pixels: - var color : Color = image.get_pixelv(i) - selected_image.set_pixelv(i, color) - image.set_pixelv(i, Color(0, 0, 0, 0)) + for x in image.get_width(): + for y in image.get_width(): + var pos := Vector2(x, y) + if project.can_pixel_get_drawn(pos): + var color : Color = image.get_pixelv(pos) + selected_image.set_pixelv(pos, color) + image.set_pixelv(pos, Color(0, 0, 0, 0)) if flip_h.pressed: selected_image.flip_x() diff --git a/src/UI/Dialogs/ImageEffects/GradientDialog.gd b/src/UI/Dialogs/ImageEffects/GradientDialog.gd index 0e0c87a40107..059c788fc7d8 100644 --- a/src/UI/Dialogs/ImageEffects/GradientDialog.gd +++ b/src/UI/Dialogs/ImageEffects/GradientDialog.gd @@ -19,7 +19,7 @@ func set_nodes() -> void: func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, _pixels) + DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, selection_checkbox.pressed, _project) func _on_ColorPickerButton_color_changed(_color : Color) -> void: diff --git a/src/UI/Dialogs/ImageEffects/HSVDialog.gd b/src/UI/Dialogs/ImageEffects/HSVDialog.gd index c77a0d694612..07966bba1b92 100644 --- a/src/UI/Dialogs/ImageEffects/HSVDialog.gd +++ b/src/UI/Dialogs/ImageEffects/HSVDialog.gd @@ -22,7 +22,7 @@ func _confirmed() -> void: func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, _pixels) + DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, selection_checkbox.pressed, _project) func reset() -> void: diff --git a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd index 08d05c662bc0..9f0c522dd6ea 100644 --- a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd +++ b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd @@ -14,7 +14,7 @@ func set_nodes() -> void: func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.invert_image_colors(_cel, _pixels, red, green, blue, alpha) + DrawingAlgos.invert_image_colors(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd index f6c1f5297d6d..3c1cafee775c 100644 --- a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd +++ b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd @@ -21,7 +21,7 @@ func set_nodes() -> void: func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.generate_outline(_cel, _pixels, color, thickness, diagonal, inside_image) + DrawingAlgos.generate_outline(_cel, selection_checkbox.pressed, _project, color, thickness, diagonal, inside_image) func _on_ThickValue_value_changed(value : int) -> void: From 0c7ed8e999221b930f93318b9150c5e8ab186694 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 04:09:01 +0300 Subject: [PATCH 38/69] Fix Selection.gd not updating when changing project --- src/Classes/Project.gd | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 2fcad8763229..7556848439c0 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -219,6 +219,7 @@ func change_project() -> void: selection_bitmap_changed() Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() + Global.canvas.selection.update() func serialize() -> Dictionary: From e647cccdf3fae17795b7941abab053f24efca5b1 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 04:33:07 +0300 Subject: [PATCH 39/69] Resize the selection bitmap along with image resize --- src/Autoload/DrawingAlgos.gd | 63 +++++++++++++++++++++--------------- src/Classes/Project.gd | 6 ++-- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 254f750f8a84..ab85d6fcdbc1 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -327,46 +327,57 @@ func resize_canvas(width : int, height : int, offset_x : int, offset_y : int) -> func general_do_scale(width : int, height : int) -> void: - var x_ratio = Global.current_project.size.x / width - var y_ratio = Global.current_project.size.y / height - var new_x_symmetry_point = Global.current_project.x_symmetry_point / x_ratio - var new_y_symmetry_point = Global.current_project.y_symmetry_point / y_ratio - var new_x_symmetry_axis_points = Global.current_project.x_symmetry_axis.points - var new_y_symmetry_axis_points = Global.current_project.y_symmetry_axis.points + var project := Global.current_project + var size := Vector2(width, height).floor() + var x_ratio = project.size.x / width + var y_ratio = project.size.y / height + + var bitmap : BitMap + bitmap = project.resize_bitmap(project.selection_bitmap, size) + + var new_x_symmetry_point = project.x_symmetry_point / x_ratio + var new_y_symmetry_point = project.y_symmetry_point / y_ratio + var new_x_symmetry_axis_points = project.x_symmetry_axis.points + var new_y_symmetry_axis_points = project.y_symmetry_axis.points new_x_symmetry_axis_points[0].y /= y_ratio new_x_symmetry_axis_points[1].y /= y_ratio new_y_symmetry_axis_points[0].x /= x_ratio new_y_symmetry_axis_points[1].x /= x_ratio - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Scale") - Global.current_project.undo_redo.add_do_property(Global.current_project, "size", Vector2(width, height).floor()) - Global.current_project.undo_redo.add_do_property(Global.current_project, "x_symmetry_point", new_x_symmetry_point) - Global.current_project.undo_redo.add_do_property(Global.current_project, "y_symmetry_point", new_y_symmetry_point) - Global.current_project.undo_redo.add_do_property(Global.current_project.x_symmetry_axis, "points", new_x_symmetry_axis_points) - Global.current_project.undo_redo.add_do_property(Global.current_project.y_symmetry_axis, "points", new_y_symmetry_axis_points) + project.undos += 1 + project.undo_redo.create_action("Scale") + project.undo_redo.add_do_property(project, "size", size) + project.undo_redo.add_do_property(project, "selection_bitmap", bitmap) + project.undo_redo.add_do_property(project, "x_symmetry_point", new_x_symmetry_point) + project.undo_redo.add_do_property(project, "y_symmetry_point", new_y_symmetry_point) + project.undo_redo.add_do_property(project.x_symmetry_axis, "points", new_x_symmetry_axis_points) + project.undo_redo.add_do_property(project.y_symmetry_axis, "points", new_y_symmetry_axis_points) func general_undo_scale() -> void: - Global.current_project.undo_redo.add_undo_property(Global.current_project, "size", Global.current_project.size) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "x_symmetry_point", Global.current_project.x_symmetry_point) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "y_symmetry_point", Global.current_project.y_symmetry_point) - Global.current_project.undo_redo.add_undo_property(Global.current_project.x_symmetry_axis, "points", Global.current_project.x_symmetry_axis.points) - Global.current_project.undo_redo.add_undo_property(Global.current_project.y_symmetry_axis, "points", Global.current_project.y_symmetry_axis.points) - Global.current_project.undo_redo.add_undo_method(Global, "undo") - Global.current_project.undo_redo.add_do_method(Global, "redo") - Global.current_project.undo_redo.commit_action() + var project := Global.current_project + project.undo_redo.add_undo_property(project, "size", project.size) + project.undo_redo.add_undo_property(project, "selection_bitmap", project.selection_bitmap) + project.undo_redo.add_undo_property(project, "x_symmetry_point", project.x_symmetry_point) + project.undo_redo.add_undo_property(project, "y_symmetry_point", project.y_symmetry_point) + project.undo_redo.add_undo_property(project.x_symmetry_axis, "points", project.x_symmetry_axis.points) + project.undo_redo.add_undo_property(project.y_symmetry_axis, "points", project.y_symmetry_axis.points) + project.undo_redo.add_undo_method(Global, "undo") + project.undo_redo.add_do_method(Global, "redo") + project.undo_redo.commit_action() func general_do_centralize() -> void: - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Centralize") + var project := Global.current_project + project.undos += 1 + project.undo_redo.create_action("Centralize") func general_undo_centralize() -> void: - Global.current_project.undo_redo.add_undo_method(Global, "undo") - Global.current_project.undo_redo.add_do_method(Global, "redo") - Global.current_project.undo_redo.commit_action() + var project := Global.current_project + project.undo_redo.add_undo_method(Global, "undo") + project.undo_redo.add_do_method(Global, "redo") + project.undo_redo.commit_action() func invert_image_colors(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 7556848439c0..64c1dfd73c0e 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -618,9 +618,9 @@ func invert_bitmap(bitmap : BitMap) -> void: # Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L605 -func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> void: +func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> BitMap: if new_size == bitmap.get_size(): - return + return bitmap var new_bitmap := BitMap.new() new_bitmap.create(new_size) var lw = min(bitmap.get_size().x, new_size.x) @@ -629,7 +629,7 @@ func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> void: for y in lh: new_bitmap.set_bit(Vector2(x, y), bitmap.get_bit(Vector2(x, y))) - bitmap = new_bitmap + return new_bitmap # Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L622 From 19a22bb9c3544dff5ca6929c96ac926f01ffed98 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 17:35:52 +0300 Subject: [PATCH 40/69] Restored rotation's old behavior and finally got rid of the selected_pixels array The rotation does not yet work properly with selections, but at least it now "works". --- src/Autoload/DrawingAlgos.gd | 234 +++++++++--------- src/Classes/ImageEffect.gd | 31 +-- src/Classes/Project.gd | 1 - .../Dialogs/ImageEffects/DesaturateDialog.gd | 2 +- .../Dialogs/ImageEffects/FlipImageDialog.gd | 2 +- src/UI/Dialogs/ImageEffects/GradientDialog.gd | 2 +- src/UI/Dialogs/ImageEffects/HSVDialog.gd | 2 +- .../ImageEffects/InvertColorsDialog.gd | 2 +- src/UI/Dialogs/ImageEffects/OutlineDialog.gd | 2 +- src/UI/Dialogs/ImageEffects/RotateImage.gd | 8 +- 10 files changed, 140 insertions(+), 146 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index ab85d6fcdbc1..f63e998c86ee 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -62,132 +62,142 @@ func scale3X(sprite : Image, tol : float = 50) -> Image: return scaled -func rotxel(sprite : Image, angle : float, pixels : Array) -> void: +func rotxel(sprite : Image, angle : float, affect_selection : bool, project : Project) -> void: # If angle is simple, then nn rotation is the best if angle == 0 || angle == PI/2 || angle == PI || angle == 2*PI: - nn_rotate(sprite, angle, pixels) + nn_rotate(sprite, angle, affect_selection, project) return var aux : Image = Image.new() aux.copy_from(sprite) - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) + var selection_rectangle := Rect2() + if affect_selection and project.has_selection: + selection_rectangle = project.get_selection_rectangle() + else: + selection_rectangle = Rect2(Vector2.ZERO, project.size) var center : Vector2 = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) var ox : int var oy : int var p : Color aux.lock() sprite.lock() - for pix in pixels: - var x = pix.x - var y = pix.y - var dx = 3*(x - center.x) - var dy = 3*(y - center.y) - var found_pixel : bool = false - for k in range(9): - var i = -1 + k % 3 + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var dx = 3*(x - center.x) + var dy = 3*(y - center.y) + var found_pixel : bool = false + for k in range(9): + var i = -1 + k % 3 # warning-ignore:integer_division - var j = -1 + int(k / 3) - var dir = atan2(dy + j, dx + i) - var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) - dir -= angle - ox = round(center.x*3 + 1 + mag*cos(dir)) - oy = round(center.y*3 + 1 + mag*sin(dir)) - - if (sprite.get_width() % 2 != 0): - ox += 1 - oy += 1 - - if (ox >= 0 && ox < sprite.get_width()*3 - && oy >= 0 && oy < sprite.get_height()*3): - found_pixel = true - break - - if !found_pixel: - sprite.set_pixel(x, y, Color(0,0,0,0)) - continue - - var fil : int = oy % 3 - var col : int = ox % 3 - var index : int = col + 3*fil - - ox = round((ox - 1)/3.0); - oy = round((oy - 1)/3.0); - var a : Color - var b : Color - var c : Color - var d : Color - var e : Color - var f : Color - var g : Color - var h : Color - var i : Color - if (ox == 0 || ox == sprite.get_width() - 1 || - oy == 0 || oy == sprite.get_height() - 1): - p = aux.get_pixel(ox, oy) - else: - a = aux.get_pixel(ox-1,oy-1); - b = aux.get_pixel(ox,oy-1); - c = aux.get_pixel(ox+1,oy-1); - d = aux.get_pixel(ox-1,oy); - e = aux.get_pixel(ox,oy); - f = aux.get_pixel(ox+1,oy); - g = aux.get_pixel(ox-1,oy+1); - h = aux.get_pixel(ox,oy+1); - i = aux.get_pixel(ox+1,oy+1); - - match(index): - 0: - p = d if (similarColors(d,b) && !similarColors(d,h) - && !similarColors(b,f)) else e; - 1: - p = b if ((similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,c)) || - (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,a))) else e; - 2: - p = f if (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h)) else e; - 3: - p = d if ((similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,a)) || - (similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,g))) else e; - 4: - p = e - 5: - p = f if((similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,i)) - || (similarColors(f,h) && !similarColors(b,f) && - !similarColors(d,h) && !similarColors(e,c))) else e; - 6: - p = d if (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b)) else e; - 7: - p = h if ((similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h) && !similarColors(e,g)) - || (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,i))) else e; - 8: - p = f if (similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h)) else e; - sprite.set_pixel(x, y, p) + var j = -1 + int(k / 3) + var dir = atan2(dy + j, dx + i) + var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) + dir -= angle + ox = round(center.x*3 + 1 + mag*cos(dir)) + oy = round(center.y*3 + 1 + mag*sin(dir)) + + if (sprite.get_width() % 2 != 0): + ox += 1 + oy += 1 + + if (ox >= 0 && ox < sprite.get_width()*3 + && oy >= 0 && oy < sprite.get_height()*3): + found_pixel = true + break + + if !found_pixel: + sprite.set_pixel(x, y, Color(0,0,0,0)) + continue + + var fil : int = oy % 3 + var col : int = ox % 3 + var index : int = col + 3*fil + + ox = round((ox - 1)/3.0); + oy = round((oy - 1)/3.0); + var a : Color + var b : Color + var c : Color + var d : Color + var e : Color + var f : Color + var g : Color + var h : Color + var i : Color + if (ox == 0 || ox == sprite.get_width() - 1 || + oy == 0 || oy == sprite.get_height() - 1): + p = aux.get_pixel(ox, oy) + else: + a = aux.get_pixel(ox-1,oy-1); + b = aux.get_pixel(ox,oy-1); + c = aux.get_pixel(ox+1,oy-1); + d = aux.get_pixel(ox-1,oy); + e = aux.get_pixel(ox,oy); + f = aux.get_pixel(ox+1,oy); + g = aux.get_pixel(ox-1,oy+1); + h = aux.get_pixel(ox,oy+1); + i = aux.get_pixel(ox+1,oy+1); + + match(index): + 0: + p = d if (similarColors(d,b) && !similarColors(d,h) + && !similarColors(b,f)) else e; + 1: + p = b if ((similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,c)) || + (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,a))) else e; + 2: + p = f if (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h)) else e; + 3: + p = d if ((similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,a)) || + (similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,g))) else e; + 4: + p = e + 5: + p = f if((similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,i)) + || (similarColors(f,h) && !similarColors(b,f) && + !similarColors(d,h) && !similarColors(e,c))) else e; + 6: + p = d if (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b)) else e; + 7: + p = h if ((similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h) && !similarColors(e,g)) + || (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,i))) else e; + 8: + p = f if (similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h)) else e; + sprite.set_pixel(x, y, p) sprite.unlock() aux.unlock() -func fake_rotsprite(sprite : Image, angle : float, pixels : Array) -> void: - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) +func fake_rotsprite(sprite : Image, angle : float, affect_selection : bool, project : Project) -> void: + var selection_rectangle := Rect2() + if affect_selection and project.has_selection: + selection_rectangle = project.get_selection_rectangle() + else: + selection_rectangle = Rect2(Vector2.ZERO, project.size) var selected_sprite := Image.new() selected_sprite = sprite.get_rect(selection_rectangle) selected_sprite.copy_from(scale3X(selected_sprite)) - nn_rotate(selected_sprite, angle, []) + nn_rotate(selected_sprite, angle, false, project) # warning-ignore:integer_division # warning-ignore:integer_division selected_sprite.resize(selected_sprite.get_width() / 3, selected_sprite.get_height() / 3, 0) sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), selection_rectangle.position) -func nn_rotate(sprite : Image, angle : float, pixels : Array) -> void: +func nn_rotate(sprite : Image, angle : float, affect_selection : bool, project : Project) -> void: var aux : Image = Image.new() aux.copy_from(sprite) sprite.lock() @@ -195,18 +205,20 @@ func nn_rotate(sprite : Image, angle : float, pixels : Array) -> void: var ox: int var oy: int var center : Vector2 - if pixels: - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) + if project.has_selection and affect_selection: + var selection_rectangle := project.get_selection_rectangle() center = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) - for pix in pixels: - var x = pix.x - var y = pix.y - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y - if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): - sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) - else: - sprite.set_pixel(x, y, Color(0,0,0,0)) + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if !project.can_pixel_get_drawn(pos): + continue + ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x + oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y + if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): + sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) + else: + sprite.set_pixel(x, y, Color(0,0,0,0)) else: # warning-ignore:integer_division # warning-ignore:integer_division diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 1b6b61eeb249..58cae215826c 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -6,7 +6,6 @@ class_name ImageEffect extends AcceptDialog enum {CEL, FRAME, ALL_FRAMES, ALL_PROJECTS} var affect : int = CEL -var pixels := [] var current_cel : Image var current_frame : Image var preview_image : Image @@ -49,39 +48,31 @@ func _about_to_show() -> void: func _confirmed() -> void: if affect == CEL: Global.canvas.handle_undo("Draw") - commit_action(current_cel, pixels) + commit_action(current_cel) Global.canvas.handle_redo("Draw") elif affect == FRAME: Global.canvas.handle_undo("Draw", Global.current_project, -1) for cel in Global.current_project.frames[Global.current_project.current_frame].cels: - commit_action(cel.image, pixels) + commit_action(cel.image) Global.canvas.handle_redo("Draw", Global.current_project, -1) elif affect == ALL_FRAMES: Global.canvas.handle_undo("Draw", Global.current_project, -1, -1) for frame in Global.current_project.frames: for cel in frame.cels: - commit_action(cel.image, pixels) + commit_action(cel.image) Global.canvas.handle_redo("Draw", Global.current_project, -1, -1) elif affect == ALL_PROJECTS: for project in Global.projects: - var _pixels := [] - if selection_checkbox.pressed and project.selected_pixels: - _pixels = project.selected_pixels.duplicate() - else: - for x in project.size.x: - for y in project.size.y: - _pixels.append(Vector2(x, y)) - Global.canvas.handle_undo("Draw", project, -1, -1) for frame in project.frames: for cel in frame.cels: - commit_action(cel.image, _pixels, project) + commit_action(cel.image, project) Global.canvas.handle_redo("Draw", project, -1, -1) -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: pass @@ -89,15 +80,7 @@ func set_nodes() -> void: pass -func _on_SelectionCheckBox_toggled(button_pressed : bool) -> void: - pixels.clear() - if button_pressed and Global.current_project.selected_pixels: - pixels = Global.current_project.selected_pixels.duplicate() - else: - for x in Global.current_project.size.x: - for y in Global.current_project.size.y: - pixels.append(Vector2(x, y)) - +func _on_SelectionCheckBox_toggled(_button_pressed : bool) -> void: update_preview() @@ -112,7 +95,7 @@ func update_preview() -> void: preview_image.copy_from(current_cel) _: preview_image.copy_from(current_frame) - commit_action(preview_image, pixels) + commit_action(preview_image) preview_image.unlock() preview_texture.create_from_image(preview_image, 0) preview.texture = preview_texture diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 64c1dfd73c0e..b1ac43ebd8e4 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -24,7 +24,6 @@ var y_symmetry_axis : SymmetryGuide var selection_bitmap := BitMap.new() var has_selection := false -var selected_pixels := [] # For every camera (currently there are 3) var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2 diff --git a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd index 43a665a684f0..ad73205f688e 100644 --- a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd +++ b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd @@ -13,7 +13,7 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: DrawingAlgos.desaturate_image(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) diff --git a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd index 3124c682f8f9..d55df164a090 100644 --- a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd +++ b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd @@ -11,7 +11,7 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, project : Project = Global.current_project) -> void: flip_image(_cel, selection_checkbox.pressed, project) diff --git a/src/UI/Dialogs/ImageEffects/GradientDialog.gd b/src/UI/Dialogs/ImageEffects/GradientDialog.gd index 059c788fc7d8..83091075d301 100644 --- a/src/UI/Dialogs/ImageEffects/GradientDialog.gd +++ b/src/UI/Dialogs/ImageEffects/GradientDialog.gd @@ -18,7 +18,7 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, selection_checkbox.pressed, _project) diff --git a/src/UI/Dialogs/ImageEffects/HSVDialog.gd b/src/UI/Dialogs/ImageEffects/HSVDialog.gd index 07966bba1b92..2ab3b4a03682 100644 --- a/src/UI/Dialogs/ImageEffects/HSVDialog.gd +++ b/src/UI/Dialogs/ImageEffects/HSVDialog.gd @@ -21,7 +21,7 @@ func _confirmed() -> void: reset() -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, selection_checkbox.pressed, _project) diff --git a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd index 9f0c522dd6ea..a1cd387e365b 100644 --- a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd +++ b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd @@ -13,7 +13,7 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: DrawingAlgos.invert_image_colors(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) diff --git a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd index 3c1cafee775c..8df28732086b 100644 --- a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd +++ b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd @@ -20,7 +20,7 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: DrawingAlgos.generate_outline(_cel, selection_checkbox.pressed, _project, color, thickness, diagonal, inside_image) diff --git a/src/UI/Dialogs/ImageEffects/RotateImage.gd b/src/UI/Dialogs/ImageEffects/RotateImage.gd index 8aa5dcd95613..fdda0b091b49 100644 --- a/src/UI/Dialogs/ImageEffects/RotateImage.gd +++ b/src/UI/Dialogs/ImageEffects/RotateImage.gd @@ -23,15 +23,15 @@ func _about_to_show() -> void: angle_hslider.value = 0 -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: var angle : float = deg2rad(angle_hslider.value) match type_option_button.text: "Rotxel": - DrawingAlgos.rotxel(_cel, angle, _pixels) + DrawingAlgos.rotxel(_cel, angle, selection_checkbox.pressed, _project) "Nearest neighbour": - DrawingAlgos.nn_rotate(_cel, angle, _pixels) + DrawingAlgos.nn_rotate(_cel, angle, selection_checkbox.pressed, _project) "Upscale, Rotate and Downscale": - DrawingAlgos.fake_rotsprite(_cel, angle, _pixels) + DrawingAlgos.fake_rotsprite(_cel, angle, selection_checkbox.pressed, _project) func _confirmed() -> void: From 2a9c2554f5571d8d9d3f3da96bcf226cf7422ea8 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 20:00:49 +0300 Subject: [PATCH 41/69] Resize selection too when using gizmos Left to do for gizmos: - Proper cancel transformation - Begin transformation (currently named move_content_start but it should be renamed to something more general) when resizing gizmos - Keep the original image and selection in memory and resize them. Meaning, gizmos should not resize the already resized data, but only resize the original. This is less destructive as there is no danger of data loss. - Always resize on InputEventMouseMotion. This is going to be worse for performance, but it will look better for the user. --- src/Classes/Project.gd | 13 +++++++++++++ src/UI/Canvas/Selection.gd | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index b1ac43ebd8e4..0a92630c6598 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -688,3 +688,16 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: image.crop(nw, nh) image.blit_rect(smaller_image, Rect2(Vector2.ZERO, size), dst) bitmap.create_from_image_alpha(image) + + +func resize_bitmap_values(bitmap : BitMap, new_size : Vector2) -> void: + var selection_node = Global.canvas.selection + var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var image : Image = bitmap_to_image(bitmap) + var selection_rect := image.get_used_rect() + var smaller_image := image.get_rect(selection_rect) + image.lock() + image.fill(Color(0)) + smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, size), selection_position) + bitmap.create_from_image_alpha(image) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index e9204a63dc97..2b61ed30488c 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -89,7 +89,12 @@ func _input(event : InputEvent) -> void: self.big_bounding_rectangle = big_bounding_rectangle.grow_individual(left, top, right, bottom) preview_image.resize(big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, Image.INTERPOLATE_NEAREST) preview_image_texture.create_from_image(preview_image, 0) + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.resize_bitmap_values(selected_bitmap_copy, big_bounding_rectangle.size) + Global.current_project.selection_bitmap = selected_bitmap_copy + Global.current_project.selection_bitmap_changed() dragged_gizmo = null + update() func _big_bounding_rectangle_changed(value : Rect2) -> void: @@ -160,6 +165,7 @@ func move_content_start() -> void: is_moving_content = true undo_data = _get_undo_data(true) get_preview_image() + update() func move_content(move : Vector2) -> void: @@ -174,8 +180,8 @@ func move_content_confirm() -> void: cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) - Global.current_project.selection_bitmap = selected_bitmap_copy + preview_image = Image.new() marching_ants_outline.offset = Vector2.ZERO is_moving_content = false From f2a38c6563fa8f1c2d5a4b2286e8a10d4304288e Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 21:32:20 +0300 Subject: [PATCH 42/69] Image and bitmap resizing now uses the original data and begin transformation on gizmo click No matter how many times the user resizes on the current transformation, the original data will not be lost until they either confirm or cancel, so there is no data loss before confirmation/cancel. --- src/Classes/Project.gd | 6 ++++-- src/UI/Canvas/Selection.gd | 35 ++++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 0a92630c6598..f7c793bcef7a 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -690,7 +690,8 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: bitmap.create_from_image_alpha(image) -func resize_bitmap_values(bitmap : BitMap, new_size : Vector2) -> void: +func resize_bitmap_values(bitmap : BitMap, new_size : Vector2) -> BitMap: + var new_bitmap := BitMap.new() var selection_node = Global.canvas.selection var selection_position : Vector2 = selection_node.big_bounding_rectangle.position var image : Image = bitmap_to_image(bitmap) @@ -700,4 +701,5 @@ func resize_bitmap_values(bitmap : BitMap, new_size : Vector2) -> void: image.fill(Color(0)) smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) image.blit_rect(smaller_image, Rect2(Vector2.ZERO, size), selection_position) - bitmap.create_from_image_alpha(image) + new_bitmap.create_from_image_alpha(image) + return new_bitmap diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 2b61ed30488c..f1a790697bea 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -31,8 +31,10 @@ class Gizmo: var clipboard := Clipboard.new() var is_moving_content := false var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed +var original_preview_image := Image.new() +var original_bitmap := BitMap.new() var preview_image := Image.new() -var preview_image_texture : ImageTexture +var preview_image_texture := ImageTexture.new() var undo_data : Dictionary var drawn_rect := Rect2(0, 0, 0, 0) var gizmos := [] # Array of Gizmos @@ -76,22 +78,20 @@ func _input(event : InputEvent) -> void: Global.has_focus = false dragged_gizmo = gizmo mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + move_content_start() elif dragged_gizmo: Global.has_focus = true var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction diff = diff.round() - print(diff) - print(preview_image.get_size()) var left := 0.0 if dragged_gizmo.direction.x >= 0 else diff.x var top := 0.0 if dragged_gizmo.direction.y >= 0 else diff.y var right := diff.x if dragged_gizmo.direction.x >= 0 else 0.0 var bottom := diff.y if dragged_gizmo.direction.y >= 0 else 0.0 self.big_bounding_rectangle = big_bounding_rectangle.grow_individual(left, top, right, bottom) + preview_image.copy_from(original_preview_image) preview_image.resize(big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, Image.INTERPOLATE_NEAREST) preview_image_texture.create_from_image(preview_image, 0) - var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() - Global.current_project.resize_bitmap_values(selected_bitmap_copy, big_bounding_rectangle.size) - Global.current_project.selection_bitmap = selected_bitmap_copy + Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, big_bounding_rectangle.size) Global.current_project.selection_bitmap_changed() dragged_gizmo = null update() @@ -165,6 +165,7 @@ func move_content_start() -> void: is_moving_content = true undo_data = _get_undo_data(true) get_preview_image() + original_bitmap = Global.current_project.selection_bitmap.duplicate() update() @@ -182,7 +183,9 @@ func move_content_confirm() -> void: Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) Global.current_project.selection_bitmap = selected_bitmap_copy + original_preview_image = Image.new() preview_image = Image.new() + original_bitmap = BitMap.new() marching_ants_outline.offset = Vector2.ZERO is_moving_content = false commit_undo("Move Selection", undo_data) @@ -200,7 +203,9 @@ func move_content_cancel() -> void: var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) + original_preview_image = Image.new() preview_image = Image.new() + original_bitmap = BitMap.new() update() @@ -361,22 +366,22 @@ func _draw() -> void: func get_preview_image() -> void: var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - if preview_image.is_empty(): -# preview_image.copy_from(cel_image) - preview_image = cel_image.get_rect(big_bounding_rectangle) - preview_image.lock() + if original_preview_image.is_empty(): +# original_preview_image.copy_from(cel_image) + original_preview_image = cel_image.get_rect(big_bounding_rectangle) + original_preview_image.lock() for x in range(0, big_bounding_rectangle.size.x): for y in range(0, big_bounding_rectangle.size.y): var pos := Vector2(x, y) if not project.selection_bitmap.get_bit(pos + big_bounding_rectangle.position): - preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) - preview_image.unlock() - preview_image_texture = ImageTexture.new() + original_preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) + original_preview_image.unlock() + preview_image.copy_from(original_preview_image) preview_image_texture.create_from_image(preview_image, 0) var clear_image := Image.new() - clear_image.create(preview_image.get_width(), preview_image.get_height(), false, Image.FORMAT_RGBA8) - cel_image.blit_rect_mask(clear_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) + clear_image.create(original_preview_image.get_width(), original_preview_image.get_height(), false, Image.FORMAT_RGBA8) + cel_image.blit_rect_mask(clear_image, original_preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) From cba510609bb7109191d2e00a13567eb52cf7757c Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 3 Apr 2021 22:34:53 +0300 Subject: [PATCH 43/69] Cancel transformation now works properly when the selection has been resized --- src/UI/Canvas/Selection.gd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index f1a790697bea..4f60b0a65239 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -31,6 +31,7 @@ class Gizmo: var clipboard := Clipboard.new() var is_moving_content := false var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed +var original_big_bounding_rectangle := Rect2() var original_preview_image := Image.new() var original_bitmap := BitMap.new() var preview_image := Image.new() @@ -166,6 +167,7 @@ func move_content_start() -> void: undo_data = _get_undo_data(true) get_preview_image() original_bitmap = Global.current_project.selection_bitmap.duplicate() + original_big_bounding_rectangle = big_bounding_rectangle update() @@ -195,10 +197,13 @@ func move_content_confirm() -> void: func move_content_cancel() -> void: if preview_image.is_empty(): return - self.big_bounding_rectangle.position -= marching_ants_outline.offset marching_ants_outline.offset = Vector2.ZERO is_moving_content = false + self.big_bounding_rectangle = original_big_bounding_rectangle + Global.current_project.selection_bitmap = original_bitmap + Global.current_project.selection_bitmap_changed() + preview_image = original_preview_image var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) From 3de7b5425423fe07c395f7a4b8da2fc35e7a4f45 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Mon, 5 Apr 2021 21:18:32 +0300 Subject: [PATCH 44/69] Made gizmos resize on mouse motion, fix issues with negative bounding rectangle and when combined with the move tool --- src/UI/Canvas/Selection.gd | 39 ++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 4f60b0a65239..cc75d0f2619b 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -31,6 +31,7 @@ class Gizmo: var clipboard := Clipboard.new() var is_moving_content := false var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed +var temp_rect := Rect2() var original_big_bounding_rectangle := Rect2() var original_preview_image := Image.new() var original_bitmap := BitMap.new() @@ -79,23 +80,33 @@ func _input(event : InputEvent) -> void: Global.has_focus = false dragged_gizmo = gizmo mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + temp_rect = big_bounding_rectangle move_content_start() + marching_ants_outline.offset = Vector2.ZERO elif dragged_gizmo: Global.has_focus = true - var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction - diff = diff.round() - var left := 0.0 if dragged_gizmo.direction.x >= 0 else diff.x - var top := 0.0 if dragged_gizmo.direction.y >= 0 else diff.y - var right := diff.x if dragged_gizmo.direction.x >= 0 else 0.0 - var bottom := diff.y if dragged_gizmo.direction.y >= 0 else 0.0 - self.big_bounding_rectangle = big_bounding_rectangle.grow_individual(left, top, right, bottom) - preview_image.copy_from(original_preview_image) - preview_image.resize(big_bounding_rectangle.size.x, big_bounding_rectangle.size.y, Image.INTERPOLATE_NEAREST) - preview_image_texture.create_from_image(preview_image, 0) - Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, big_bounding_rectangle.size) - Global.current_project.selection_bitmap_changed() dragged_gizmo = null - update() + + if dragged_gizmo: + var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction + var dir := dragged_gizmo.direction + if diff != Vector2.ZERO: + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + var left := 0.0 if dir.x >= 0 else diff.x + var top := 0.0 if dir.y >= 0 else diff.y + var right := diff.x if dir.x >= 0 else 0.0 + var bottom := diff.y if dir.y >= 0 else 0.0 + temp_rect = temp_rect.grow_individual(left, top, right, bottom) + big_bounding_rectangle = temp_rect.abs() + big_bounding_rectangle.position = big_bounding_rectangle.position.ceil() + self.big_bounding_rectangle.size = big_bounding_rectangle.size.ceil() + var size = big_bounding_rectangle.size.abs() + preview_image.copy_from(original_preview_image) + preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + preview_image_texture.create_from_image(preview_image, 0) + Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size) + Global.current_project.selection_bitmap_changed() + update() func _big_bounding_rectangle_changed(value : Rect2) -> void: @@ -354,7 +365,7 @@ func _draw() -> void: _scale.x = -1 draw_set_transform(_position, rotation, _scale) draw_rect(drawn_rect, Color.black, false) - if big_bounding_rectangle.size > Vector2.ZERO: + if big_bounding_rectangle.size != Vector2.ZERO: for gizmo in gizmos: # Draw gizmos draw_rect(gizmo.rect, Color.black) var filled_rect : Rect2 = gizmo.rect From c2a1a4a5521e11f339ff2df1875f7c896cd5de08 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 6 Apr 2021 03:47:25 +0300 Subject: [PATCH 45/69] Resizing can now get out of positive bounds, clearing and inverting now gets limited to the canvas bounds Resizing currently does not work properly with negative (left & up) canvas boundaries --- src/Classes/Project.gd | 9 +++++++-- src/UI/Canvas/Selection.gd | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index f7c793bcef7a..412b434f3d78 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -691,15 +691,20 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: func resize_bitmap_values(bitmap : BitMap, new_size : Vector2) -> BitMap: - var new_bitmap := BitMap.new() var selection_node = Global.canvas.selection var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var new_bitmap_size := Vector2() + new_bitmap_size.x = max(size.x, selection_position.x + new_size.x) + new_bitmap_size.y = max(size.y, selection_position.y + new_size.y) + var new_bitmap := BitMap.new() var image : Image = bitmap_to_image(bitmap) var selection_rect := image.get_used_rect() var smaller_image := image.get_rect(selection_rect) image.lock() image.fill(Color(0)) smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(smaller_image, Rect2(Vector2.ZERO, size), selection_position) + if new_bitmap_size != size: + image.crop(new_bitmap_size.x, new_bitmap_size.y) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), selection_position) new_bitmap.create_from_image_alpha(image) return new_bitmap diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index cc75d0f2619b..d40f2e4ba024 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -335,8 +335,10 @@ func invert() -> void: var project := Global.current_project var _undo_data = _get_undo_data(false) var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + selection_bitmap_copy = project.resize_bitmap(selection_bitmap_copy, project.size) project.invert_bitmap(selection_bitmap_copy) project.selection_bitmap = selection_bitmap_copy + Global.current_project.selection_bitmap_changed() self.big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) commit_undo("Rectangle Select", _undo_data) @@ -346,8 +348,9 @@ func clear_selection(use_undo := false) -> void: if !project.has_selection: return move_content_confirm() - var full_rect = Rect2(Vector2.ZERO, project.selection_bitmap.get_size()) var _undo_data = _get_undo_data(false) + project.selection_bitmap = project.resize_bitmap(project.selection_bitmap, project.size) + var full_rect = Rect2(Vector2.ZERO, project.selection_bitmap.get_size()) select_rect(full_rect, false) self.big_bounding_rectangle = Rect2() From a80d366eb58e27764ae38d0b91b9b2d23bb79201 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 6 Apr 2021 19:11:58 +0300 Subject: [PATCH 46/69] Flip image when resizing and the bounding rectangle gets flipped --- src/UI/Canvas/Selection.gd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index d40f2e4ba024..68b9764b5669 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -103,6 +103,10 @@ func _input(event : InputEvent) -> void: var size = big_bounding_rectangle.size.abs() preview_image.copy_from(original_preview_image) preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + if temp_rect.size.x < 0: + preview_image.flip_x() + if temp_rect.size.y < 0: + preview_image.flip_x() preview_image_texture.create_from_image(preview_image, 0) Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size) Global.current_project.selection_bitmap_changed() From bb2f4852a618fe1ff487c9c19016cebf3bf9eef9 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 6 Apr 2021 19:22:26 +0300 Subject: [PATCH 47/69] Call move_content_confirm() when inverting selection --- src/UI/Canvas/Selection.gd | 1 + 1 file changed, 1 insertion(+) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 68b9764b5669..c8e1733c5d4b 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -336,6 +336,7 @@ func select_all() -> void: func invert() -> void: + move_content_confirm() var project := Global.current_project var _undo_data = _get_undo_data(false) var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() From 06a0255ccfa4f839007f802311d5f15511bdeaf2 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 8 Apr 2021 03:51:35 +0300 Subject: [PATCH 48/69] Attempt to implement selection resizing that goes outside of the canvas boundaries (not working properly yet) --- src/Classes/Project.gd | 17 ++++++++++++----- src/UI/Canvas/Selection.gd | 4 +++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 412b434f3d78..715a4607572e 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -690,21 +690,28 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: bitmap.create_from_image_alpha(image) -func resize_bitmap_values(bitmap : BitMap, new_size : Vector2) -> BitMap: +func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_h : bool, flip_v : bool) -> BitMap: var selection_node = Global.canvas.selection var selection_position : Vector2 = selection_node.big_bounding_rectangle.position - var new_bitmap_size := Vector2() - new_bitmap_size.x = max(size.x, selection_position.x + new_size.x) - new_bitmap_size.y = max(size.y, selection_position.y + new_size.y) + var dst := selection_position + var new_bitmap_size := size + new_bitmap_size.x = max(size.x, abs(selection_position.x) + new_size.x) + new_bitmap_size.y = max(size.y, abs(selection_position.y) + new_size.y) var new_bitmap := BitMap.new() var image : Image = bitmap_to_image(bitmap) var selection_rect := image.get_used_rect() var smaller_image := image.get_rect(selection_rect) + if selection_position.x < 0: + selection_node.marching_ants_outline.offset.x = selection_position.x + dst.x = 0 + if selection_position.y < 0: + selection_node.marching_ants_outline.offset.y = selection_position.y + dst.y = 0 image.lock() image.fill(Color(0)) smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) if new_bitmap_size != size: image.crop(new_bitmap_size.x, new_bitmap_size.y) - image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), selection_position) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), dst) new_bitmap.create_from_image_alpha(image) return new_bitmap diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index c8e1733c5d4b..9a175c4e4b92 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -203,7 +203,7 @@ func move_content_confirm() -> void: original_preview_image = Image.new() preview_image = Image.new() original_bitmap = BitMap.new() - marching_ants_outline.offset = Vector2.ZERO +# marching_ants_outline.offset = Vector2.ZERO is_moving_content = false commit_undo("Move Selection", undo_data) update() @@ -397,6 +397,8 @@ func get_preview_image() -> void: for x in range(0, big_bounding_rectangle.size.x): for y in range(0, big_bounding_rectangle.size.y): var pos := Vector2(x, y) + if big_bounding_rectangle.position.x < 0 or big_bounding_rectangle.position.y < 0: + continue if not project.selection_bitmap.get_bit(pos + big_bounding_rectangle.position): original_preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) original_preview_image.unlock() From c1c9810e1c68bf29ab244380d4f5200a6ca15599 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 8 Apr 2021 03:59:11 +0300 Subject: [PATCH 49/69] Flip selection when resizing to negative bounding rectangle sizes And fix preview_image vertical flipping --- src/Classes/Project.gd | 6 +++++- src/UI/Canvas/Selection.gd | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 715a4607572e..62354d68d2b0 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -690,7 +690,7 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: bitmap.create_from_image_alpha(image) -func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_h : bool, flip_v : bool) -> BitMap: +func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_x : bool, flip_y : bool) -> BitMap: var selection_node = Global.canvas.selection var selection_position : Vector2 = selection_node.big_bounding_rectangle.position var dst := selection_position @@ -710,6 +710,10 @@ func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_h : bool, fl image.lock() image.fill(Color(0)) smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) + if flip_x: + smaller_image.flip_x() + if flip_y: + smaller_image.flip_y() if new_bitmap_size != size: image.crop(new_bitmap_size.x, new_bitmap_size.y) image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), dst) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 9a175c4e4b92..fb085044ea76 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -106,9 +106,9 @@ func _input(event : InputEvent) -> void: if temp_rect.size.x < 0: preview_image.flip_x() if temp_rect.size.y < 0: - preview_image.flip_x() + preview_image.flip_y() preview_image_texture.create_from_image(preview_image, 0) - Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size) + Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size, temp_rect.size.x < 0, temp_rect.size.y < 0) Global.current_project.selection_bitmap_changed() update() From 2dd1ade803806027e5c06ff8427c5c9e517a6d35 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 8 Apr 2021 17:58:24 +0300 Subject: [PATCH 50/69] Fix rotation so that it works (almost) properly with selections Rotation algorithms now accept and only work with a given image, and the pivot has been added as a parameter --- src/Autoload/DrawingAlgos.gd | 77 +++++++--------------- src/UI/Dialogs/ImageEffects/RotateImage.gd | 29 +++++++- 2 files changed, 48 insertions(+), 58 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index f63e998c86ee..96029955622a 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -5,7 +5,7 @@ enum GradientDirection {TOP, BOTTOM, LEFT, RIGHT} func scale3X(sprite : Image, tol : float = 50) -> Image: - var scaled = Image.new() + var scaled := Image.new() scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) scaled.lock() sprite.lock() @@ -62,32 +62,23 @@ func scale3X(sprite : Image, tol : float = 50) -> Image: return scaled -func rotxel(sprite : Image, angle : float, affect_selection : bool, project : Project) -> void: +func rotxel(sprite : Image, angle : float, pivot : Vector2) -> void: # If angle is simple, then nn rotation is the best if angle == 0 || angle == PI/2 || angle == PI || angle == 2*PI: - nn_rotate(sprite, angle, affect_selection, project) + nn_rotate(sprite, angle, pivot) return var aux : Image = Image.new() aux.copy_from(sprite) - var selection_rectangle := Rect2() - if affect_selection and project.has_selection: - selection_rectangle = project.get_selection_rectangle() - else: - selection_rectangle = Rect2(Vector2.ZERO, project.size) - var center : Vector2 = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) var ox : int var oy : int var p : Color aux.lock() sprite.lock() - for x in project.size.x: - for y in project.size.y: - var pos := Vector2(x, y) - if affect_selection and !project.can_pixel_get_drawn(pos): - continue - var dx = 3*(x - center.x) - var dy = 3*(y - center.y) + for x in sprite.get_size().x: + for y in sprite.get_size().y: + var dx = 3*(x - pivot.x) + var dy = 3*(y - pivot.y) var found_pixel : bool = false for k in range(9): var i = -1 + k % 3 @@ -96,8 +87,8 @@ func rotxel(sprite : Image, angle : float, affect_selection : bool, project : Pr var dir = atan2(dy + j, dx + i) var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) dir -= angle - ox = round(center.x*3 + 1 + mag*cos(dir)) - oy = round(center.y*3 + 1 + mag*sin(dir)) + ox = round(pivot.x*3 + 1 + mag*cos(dir)) + oy = round(pivot.y*3 + 1 + mag*sin(dir)) if (sprite.get_width() % 2 != 0): ox += 1 @@ -181,56 +172,32 @@ func rotxel(sprite : Image, angle : float, affect_selection : bool, project : Pr aux.unlock() -func fake_rotsprite(sprite : Image, angle : float, affect_selection : bool, project : Project) -> void: - var selection_rectangle := Rect2() - if affect_selection and project.has_selection: - selection_rectangle = project.get_selection_rectangle() - else: - selection_rectangle = Rect2(Vector2.ZERO, project.size) +func fake_rotsprite(sprite : Image, angle : float, pivot : Vector2) -> void: var selected_sprite := Image.new() - selected_sprite = sprite.get_rect(selection_rectangle) + selected_sprite.copy_from(sprite) selected_sprite.copy_from(scale3X(selected_sprite)) - nn_rotate(selected_sprite, angle, false, project) + nn_rotate(selected_sprite, angle, pivot * 3) # warning-ignore:integer_division # warning-ignore:integer_division selected_sprite.resize(selected_sprite.get_width() / 3, selected_sprite.get_height() / 3, 0) - sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), selection_rectangle.position) + sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), Vector2.ZERO) -func nn_rotate(sprite : Image, angle : float, affect_selection : bool, project : Project) -> void: +func nn_rotate(sprite : Image, angle : float, pivot : Vector2) -> void: var aux : Image = Image.new() aux.copy_from(sprite) sprite.lock() aux.lock() var ox: int var oy: int - var center : Vector2 - if project.has_selection and affect_selection: - var selection_rectangle := project.get_selection_rectangle() - center = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) - for x in project.size.x: - for y in project.size.y: - var pos := Vector2(x, y) - if !project.can_pixel_get_drawn(pos): - continue - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y - if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): - sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) - else: - sprite.set_pixel(x, y, Color(0,0,0,0)) - else: -# warning-ignore:integer_division -# warning-ignore:integer_division - center = Vector2(sprite.get_width() / 2, sprite.get_height() / 2) - for x in range(sprite.get_width()): - for y in range(sprite.get_height()): - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y - if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): - sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) - else: - sprite.set_pixel(x, y, Color(0,0,0,0)) + for x in range(sprite.get_width()): + for y in range(sprite.get_height()): + ox = (x - pivot.x)*cos(angle) + (y - pivot.y)*sin(angle) + pivot.x + oy = -(x - pivot.x)*sin(angle) + (y - pivot.y)*cos(angle) + pivot.y + if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): + sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) + else: + sprite.set_pixel(x, y, Color(0,0,0,0)) sprite.unlock() aux.unlock() diff --git a/src/UI/Dialogs/ImageEffects/RotateImage.gd b/src/UI/Dialogs/ImageEffects/RotateImage.gd index fdda0b091b49..465ef6322c23 100644 --- a/src/UI/Dialogs/ImageEffects/RotateImage.gd +++ b/src/UI/Dialogs/ImageEffects/RotateImage.gd @@ -25,13 +25,36 @@ func _about_to_show() -> void: func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: var angle : float = deg2rad(angle_hslider.value) +# warning-ignore:integer_division +# warning-ignore:integer_division + var pivot = Vector2(_cel.get_width() / 2, _cel.get_height() / 2) + var image := Image.new() + image.copy_from(_cel) + if _project.has_selection and selection_checkbox.pressed: + var selection_rectangle : Rect2 = _project.get_selection_rectangle() + pivot = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) + image.lock() + _cel.lock() + for x in _project.size.x: + for y in _project.size.y: + var pos := Vector2(x, y) + if !_project.can_pixel_get_drawn(pos): + image.set_pixelv(pos, Color(0, 0, 0, 0)) + else: + _cel.set_pixelv(pos, Color(0, 0, 0, 0)) + image.unlock() + _cel.unlock() match type_option_button.text: "Rotxel": - DrawingAlgos.rotxel(_cel, angle, selection_checkbox.pressed, _project) + DrawingAlgos.rotxel(image, angle, pivot) "Nearest neighbour": - DrawingAlgos.nn_rotate(_cel, angle, selection_checkbox.pressed, _project) + DrawingAlgos.nn_rotate(image, angle, pivot) "Upscale, Rotate and Downscale": - DrawingAlgos.fake_rotsprite(_cel, angle, selection_checkbox.pressed, _project) + DrawingAlgos.fake_rotsprite(image, angle, pivot) + if _project.has_selection and selection_checkbox.pressed: + _cel.blend_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) + else: + _cel.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) func _confirmed() -> void: From 050ec26240f29b7023a3daa82f4737457944dfec Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Tue, 13 Apr 2021 21:34:32 +0300 Subject: [PATCH 51/69] Experimental gizmo rotation - does not work properly yet Transforming the selection outside of the canvas is still broken. --- src/UI/Canvas/Selection.gd | 131 +++++++++++++++++++++++++++---------- 1 file changed, 98 insertions(+), 33 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index fb085044ea76..5cf5e93af159 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -40,7 +40,10 @@ var preview_image_texture := ImageTexture.new() var undo_data : Dictionary var drawn_rect := Rect2(0, 0, 0, 0) var gizmos := [] # Array of Gizmos +var rotation_gizmo := Rect2() var dragged_gizmo : Gizmo = null +var is_rotating := false +var prev_angle := 0 var mouse_pos_on_gizmo_drag := Vector2.ZERO onready var marching_ants_outline : Sprite = $MarchingAntsOutline @@ -66,6 +69,7 @@ func _input(event : InputEvent) -> void: move_content_cancel() elif event is InputEventMouse: var gizmo + var rot_gizmo := rotation_gizmo.has_point(Global.canvas.current_pixel) for g in gizmos: if g.rect.has_point(Global.canvas.current_pixel): gizmo = g @@ -76,41 +80,34 @@ func _input(event : InputEvent) -> void: Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: - if gizmo and event.pressed: - Global.has_focus = false - dragged_gizmo = gizmo - mouse_pos_on_gizmo_drag = Global.canvas.current_pixel - temp_rect = big_bounding_rectangle - move_content_start() - marching_ants_outline.offset = Vector2.ZERO + if event.pressed: + if gizmo: + Global.has_focus = false + dragged_gizmo = gizmo + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + temp_rect = big_bounding_rectangle + move_content_start() + marching_ants_outline.offset = Vector2.ZERO + if rot_gizmo: + Global.has_focus = false + is_rotating = true + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + temp_rect = big_bounding_rectangle + move_content_start() + var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) + original_preview_image.crop(img_size, img_size) + marching_ants_outline.offset = Vector2.ZERO elif dragged_gizmo: Global.has_focus = true dragged_gizmo = null + elif is_rotating: + Global.has_focus = true + is_rotating = false if dragged_gizmo: - var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction - var dir := dragged_gizmo.direction - if diff != Vector2.ZERO: - mouse_pos_on_gizmo_drag = Global.canvas.current_pixel - var left := 0.0 if dir.x >= 0 else diff.x - var top := 0.0 if dir.y >= 0 else diff.y - var right := diff.x if dir.x >= 0 else 0.0 - var bottom := diff.y if dir.y >= 0 else 0.0 - temp_rect = temp_rect.grow_individual(left, top, right, bottom) - big_bounding_rectangle = temp_rect.abs() - big_bounding_rectangle.position = big_bounding_rectangle.position.ceil() - self.big_bounding_rectangle.size = big_bounding_rectangle.size.ceil() - var size = big_bounding_rectangle.size.abs() - preview_image.copy_from(original_preview_image) - preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - if temp_rect.size.x < 0: - preview_image.flip_x() - if temp_rect.size.y < 0: - preview_image.flip_y() - preview_image_texture.create_from_image(preview_image, 0) - Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size, temp_rect.size.x < 0, temp_rect.size.y < 0) - Global.current_project.selection_bitmap_changed() - update() + gizmo_resize() + if is_rotating: + gizmo_rotate() func _big_bounding_rectangle_changed(value : Rect2) -> void: @@ -128,6 +125,66 @@ func _big_bounding_rectangle_changed(value : Rect2) -> void: gizmos[6].rect = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) gizmos[7].rect = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + rotation_gizmo = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - 2), size) + + +func gizmo_resize() -> void: + var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction + var dir := dragged_gizmo.direction + if diff != Vector2.ZERO: + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + var left := 0.0 if dir.x >= 0 else diff.x + var top := 0.0 if dir.y >= 0 else diff.y + var right := diff.x if dir.x >= 0 else 0.0 + var bottom := diff.y if dir.y >= 0 else 0.0 + temp_rect = temp_rect.grow_individual(left, top, right, bottom) + big_bounding_rectangle = temp_rect.abs() + big_bounding_rectangle.position = big_bounding_rectangle.position.ceil() + self.big_bounding_rectangle.size = big_bounding_rectangle.size.ceil() + var size = big_bounding_rectangle.size.abs() + preview_image.copy_from(original_preview_image) + preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + if temp_rect.size.x < 0: + preview_image.flip_x() + if temp_rect.size.y < 0: + preview_image.flip_y() + preview_image_texture.create_from_image(preview_image, 0) + Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size, temp_rect.size.x < 0, temp_rect.size.y < 0) + Global.current_project.selection_bitmap_changed() + update() + + +func gizmo_rotate() -> void: + var angle := Global.canvas.current_pixel.angle_to_point(mouse_pos_on_gizmo_drag) + angle = deg2rad(floor(rad2deg(angle))) + if angle == prev_angle: + print("hm") + return + prev_angle = angle +# print(rad2deg(angle)) +# var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) +# warning-ignore:integer_division +# warning-ignore:integer_division +# var pivot = Vector2(original_preview_image.get_width() / 2, original_preview_image.get_height() / 2) + var pivot = Vector2(big_bounding_rectangle.size.x / 2, big_bounding_rectangle.size.y / 2) + preview_image.copy_from(original_preview_image) + if temp_rect.position != big_bounding_rectangle.position: + preview_image.fill(Color(0, 0, 0, 0)) + var pos_diff := (temp_rect.position - big_bounding_rectangle.position).abs() +# pos_diff.y = 0 + preview_image.blit_rect(original_preview_image, Rect2(Vector2.ZERO, preview_image.get_size()), pos_diff) + DrawingAlgos.nn_rotate(preview_image, angle, pivot) + preview_image_texture.create_from_image(preview_image, 0) + + var bitmap_image = Global.current_project.bitmap_to_image(original_bitmap) + var bitmap_pivot = temp_rect.position + ((temp_rect.end - temp_rect.position) / 2) + DrawingAlgos.nn_rotate(bitmap_image, angle, bitmap_pivot) + Global.current_project.selection_bitmap.create_from_image_alpha(bitmap_image) + Global.current_project.selection_bitmap_changed() + self.big_bounding_rectangle = bitmap_image.get_used_rect() +# print(big_bounding_rectangle) + update() + func move_borders_start() -> void: undo_data = _get_undo_data(false) @@ -199,6 +256,8 @@ func move_content_confirm() -> void: var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) Global.current_project.selection_bitmap = selected_bitmap_copy + var bitmap_image = Global.current_project.bitmap_to_image(selected_bitmap_copy) + self.big_bounding_rectangle = bitmap_image.get_used_rect() original_preview_image = Image.new() preview_image = Image.new() @@ -382,6 +441,13 @@ func _draw() -> void: filled_rect.size -= filled_size * 2 draw_rect(filled_rect, Color.white) # Filled white square + draw_rect(rotation_gizmo, Color.black) + var filled_rect : Rect2 = rotation_gizmo + var filled_size := Vector2(0.2, 0.2) + filled_rect.position += filled_size + filled_rect.size -= filled_size * 2 + draw_rect(filled_rect, Color.white) # Filled white square + if is_moving_content and !preview_image.is_empty(): draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) draw_set_transform(position, rotation, scale) @@ -397,10 +463,9 @@ func get_preview_image() -> void: for x in range(0, big_bounding_rectangle.size.x): for y in range(0, big_bounding_rectangle.size.y): var pos := Vector2(x, y) - if big_bounding_rectangle.position.x < 0 or big_bounding_rectangle.position.y < 0: - continue - if not project.selection_bitmap.get_bit(pos + big_bounding_rectangle.position): + if !project.can_pixel_get_drawn(pos + big_bounding_rectangle.position): original_preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) + original_preview_image.unlock() preview_image.copy_from(original_preview_image) preview_image_texture.create_from_image(preview_image, 0) From 752f5dff2cc70810a6d18697d9f0f92cf256a8ad Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Wed, 14 Apr 2021 21:03:00 +0300 Subject: [PATCH 52/69] Fix some issues with moving selection out of canvas bounds --- src/Classes/Project.gd | 21 ++++++++++++++++----- src/Tools/Bucket.gd | 4 ++-- src/UI/Canvas/Selection.gd | 14 +++++++------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 62354d68d2b0..65dd814ca25d 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -596,13 +596,13 @@ func is_empty() -> bool: func can_pixel_get_drawn(pixel : Vector2) -> bool: + if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: + return false var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position if selection_position.x < 0: pixel.x -= selection_position.x if selection_position.y < 0: pixel.y -= selection_position.y - if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: - return false if has_selection: return selection_bitmap.get_bit(pixel) else: @@ -664,6 +664,8 @@ func get_selection_rectangle(bitmap : BitMap = selection_bitmap) -> Rect2: func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: var selection_node = Global.canvas.selection var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var selection_end : Vector2 = selection_node.big_bounding_rectangle.end + var image : Image = bitmap_to_image(bitmap) var selection_rect := image.get_used_rect() var smaller_image := image.get_rect(selection_rect) @@ -671,8 +673,8 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: image.fill(Color(0)) selection_rect.position += to var dst := selection_position - var x_diff = selection_rect.end.x - size.x - var y_diff = selection_rect.end.y - size.y + var x_diff = selection_end.x - size.x + var y_diff = selection_end.y - size.y var nw = max(size.x, size.x + x_diff) var nh = max(size.y, size.y + y_diff) @@ -680,13 +682,22 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: nw -= selection_position.x selection_node.marching_ants_outline.offset.x = selection_position.x dst.x = 0 + else: + selection_node.marching_ants_outline.offset.x = 0 if selection_position.y < 0: nh -= selection_position.y selection_node.marching_ants_outline.offset.y = selection_position.y dst.y = 0 + else: + selection_node.marching_ants_outline.offset.y = 0 + + if nw <= image.get_size().x: + nw = image.get_size().x + if nh <= image.get_size().y: + nh = image.get_size().y image.crop(nw, nh) - image.blit_rect(smaller_image, Rect2(Vector2.ZERO, size), dst) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, Vector2(nw, nh)), dst) bitmap.create_from_image_alpha(image) diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index c923e6d1ddff..af61e54cdaae 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -97,7 +97,7 @@ func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_confirm() if Global.current_project.layers[Global.current_project.current_layer].locked or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position): return - if Global.current_project.has_selection and not Global.current_project.selection_bitmap.get_bit(position): + if Global.current_project.has_selection and not Global.current_project.can_pixel_get_drawn(position): return var undo_data = _get_undo_data() if _fill_area == 0: @@ -128,7 +128,7 @@ func fill_in_color(position : Vector2) -> void: for x in Global.current_project.size.x: for y in Global.current_project.size.y: var pos := Vector2(x, y) - if project.has_selection and not project.selection_bitmap.get_bit(pos): + if project.has_selection and not project.can_pixel_get_drawn(pos): continue if image.get_pixelv(pos).is_equal_approx(color): _set_pixel(image, x, y, tool_slot.color) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 5cf5e93af159..8eb2ea264bdc 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -35,6 +35,7 @@ var temp_rect := Rect2() var original_big_bounding_rectangle := Rect2() var original_preview_image := Image.new() var original_bitmap := BitMap.new() +var original_offset := Vector2.ZERO var preview_image := Image.new() var preview_image_texture := ImageTexture.new() var undo_data : Dictionary @@ -168,16 +169,16 @@ func gizmo_rotate() -> void: # var pivot = Vector2(original_preview_image.get_width() / 2, original_preview_image.get_height() / 2) var pivot = Vector2(big_bounding_rectangle.size.x / 2, big_bounding_rectangle.size.y / 2) preview_image.copy_from(original_preview_image) - if temp_rect.position != big_bounding_rectangle.position: + if original_big_bounding_rectangle.position != big_bounding_rectangle.position: preview_image.fill(Color(0, 0, 0, 0)) - var pos_diff := (temp_rect.position - big_bounding_rectangle.position).abs() + var pos_diff := (original_big_bounding_rectangle.position - big_bounding_rectangle.position).abs() # pos_diff.y = 0 preview_image.blit_rect(original_preview_image, Rect2(Vector2.ZERO, preview_image.get_size()), pos_diff) DrawingAlgos.nn_rotate(preview_image, angle, pivot) preview_image_texture.create_from_image(preview_image, 0) var bitmap_image = Global.current_project.bitmap_to_image(original_bitmap) - var bitmap_pivot = temp_rect.position + ((temp_rect.end - temp_rect.position) / 2) + var bitmap_pivot = original_big_bounding_rectangle.position + ((original_big_bounding_rectangle.end - original_big_bounding_rectangle.position) / 2) DrawingAlgos.nn_rotate(bitmap_image, angle, bitmap_pivot) Global.current_project.selection_bitmap.create_from_image_alpha(bitmap_image) Global.current_project.selection_bitmap_changed() @@ -240,6 +241,7 @@ func move_content_start() -> void: get_preview_image() original_bitmap = Global.current_project.selection_bitmap.duplicate() original_big_bounding_rectangle = big_bounding_rectangle + original_offset = marching_ants_outline.offset update() @@ -256,8 +258,6 @@ func move_content_confirm() -> void: var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) Global.current_project.selection_bitmap = selected_bitmap_copy - var bitmap_image = Global.current_project.bitmap_to_image(selected_bitmap_copy) - self.big_bounding_rectangle = bitmap_image.get_used_rect() original_preview_image = Image.new() preview_image = Image.new() @@ -271,7 +271,7 @@ func move_content_confirm() -> void: func move_content_cancel() -> void: if preview_image.is_empty(): return - marching_ants_outline.offset = Vector2.ZERO + marching_ants_outline.offset = original_offset is_moving_content = false self.big_bounding_rectangle = original_big_bounding_rectangle @@ -415,7 +415,7 @@ func clear_selection(use_undo := false) -> void: var _undo_data = _get_undo_data(false) project.selection_bitmap = project.resize_bitmap(project.selection_bitmap, project.size) var full_rect = Rect2(Vector2.ZERO, project.selection_bitmap.get_size()) - select_rect(full_rect, false) + project.selection_bitmap.set_bit_rect(full_rect, false) self.big_bounding_rectangle = Rect2() marching_ants_outline.offset = Vector2.ZERO From 157a3d30db9b2aca7b8231058ff8acb0ae5db993 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Wed, 14 Apr 2021 21:42:59 +0300 Subject: [PATCH 53/69] Fix more issues with selection getting resized outside of canvas bounds --- src/UI/Canvas/Selection.gd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 8eb2ea264bdc..8eaa479ba518 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -159,7 +159,6 @@ func gizmo_rotate() -> void: var angle := Global.canvas.current_pixel.angle_to_point(mouse_pos_on_gizmo_drag) angle = deg2rad(floor(rad2deg(angle))) if angle == prev_angle: - print("hm") return prev_angle = angle # print(rad2deg(angle)) @@ -254,7 +253,7 @@ func move_content_confirm() -> void: return var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) Global.current_project.selection_bitmap = selected_bitmap_copy @@ -280,7 +279,7 @@ func move_content_cancel() -> void: preview_image = original_preview_image var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image - cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) original_preview_image = Image.new() preview_image = Image.new() @@ -460,6 +459,7 @@ func get_preview_image() -> void: # original_preview_image.copy_from(cel_image) original_preview_image = cel_image.get_rect(big_bounding_rectangle) original_preview_image.lock() + # For non-rectangular selections for x in range(0, big_bounding_rectangle.size.x): for y in range(0, big_bounding_rectangle.size.y): var pos := Vector2(x, y) @@ -472,7 +472,7 @@ func get_preview_image() -> void: var clear_image := Image.new() clear_image.create(original_preview_image.get_width(), original_preview_image.get_height(), false, Image.FORMAT_RGBA8) - cel_image.blit_rect_mask(clear_image, original_preview_image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) + cel_image.blit_rect_mask(clear_image, original_preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) From a98e1e6659aae2b659bf9b5fa81f61469e294510 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 15 Apr 2021 17:39:41 +0300 Subject: [PATCH 54/69] Update marching ants effect properly when switching between projects And make sure the frequency of the marching ants effect always looks roughly the same on all project sizes --- src/Classes/Project.gd | 16 +++++++--------- src/UI/Canvas/CameraMovement.gd | 8 ++++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 65dd814ca25d..6929132ce8dd 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -174,21 +174,12 @@ func change_project() -> void: for brush in brushes: Brushes.add_project_brush(brush) - var cameras = [Global.camera, Global.camera2, Global.camera_preview] - var i := 0 - for camera in cameras: - camera.zoom = cameras_zoom[i] - camera.offset = cameras_offset[i] - i += 1 - Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %" Global.canvas.update() Global.canvas.grid.update() - Global.canvas.pixel_grid.update() Global.transparent_checker._ready() Global.animation_timeline.fps_spinbox.value = fps Global.horizontal_ruler.update() Global.vertical_ruler.update() - Global.preview_zoom_slider.value = -Global.camera_preview.zoom.x Global.cursor_position_label.text = "[%s×%s]" % [size.x, size.y] Global.window_title = "%s - Pixelorama %s" % [name, Global.current_version] @@ -220,6 +211,13 @@ func change_project() -> void: Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() Global.canvas.selection.update() + var i := 0 + for camera in [Global.camera, Global.camera2, Global.camera_preview]: + camera.zoom = cameras_zoom[i] + camera.offset = cameras_offset[i] + camera.zoom_changed() + i += 1 + func serialize() -> Dictionary: var layer_data := [] diff --git a/src/UI/Canvas/CameraMovement.gd b/src/UI/Canvas/CameraMovement.gd index a90008355490..088033c2e0af 100644 --- a/src/UI/Canvas/CameraMovement.gd +++ b/src/UI/Canvas/CameraMovement.gd @@ -178,8 +178,12 @@ func zoom_changed() -> void: update_rulers() for guide in Global.current_project.guides: guide.width = zoom.x * 2 - Global.canvas.selection.marching_ants_outline.material.set_shader_param("width", zoom.x) - Global.canvas.selection.marching_ants_outline.material.set_shader_param("frequency", (1.0 / zoom.x) * 10) + + var marching_ants : Sprite = Global.canvas.selection.marching_ants_outline + var size := max(Global.current_project.selection_bitmap.get_size().x, Global.current_project.selection_bitmap.get_size().y) + marching_ants.material.set_shader_param("width", zoom.x) + marching_ants.material.set_shader_param("frequency", (1.0 / zoom.x) * 10 * size / 64) + elif name == "CameraPreview": Global.preview_zoom_slider.value = -zoom.x From 973346d364755e5fde69cd4d2fe74c188820ad2d Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Thu, 15 Apr 2021 21:48:12 +0300 Subject: [PATCH 55/69] Made the rotation gizmo part of the gizmos array and resize them based on camera zoom --- src/UI/Canvas/CameraMovement.gd | 4 ++ src/UI/Canvas/Selection.gd | 75 ++++++++++++++++----------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/src/UI/Canvas/CameraMovement.gd b/src/UI/Canvas/CameraMovement.gd index 088033c2e0af..bdc19602f1cc 100644 --- a/src/UI/Canvas/CameraMovement.gd +++ b/src/UI/Canvas/CameraMovement.gd @@ -183,6 +183,10 @@ func zoom_changed() -> void: var size := max(Global.current_project.selection_bitmap.get_size().x, Global.current_project.selection_bitmap.get_size().y) marching_ants.material.set_shader_param("width", zoom.x) marching_ants.material.set_shader_param("frequency", (1.0 / zoom.x) * 10 * size / 64) + for gizmo in Global.canvas.selection.gizmos: + if gizmo.rect.size == Vector2.ZERO: + return + Global.canvas.selection.update_gizmos() elif name == "CameraPreview": Global.preview_zoom_slider.value = -zoom.x diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 8eaa479ba518..43c950a770e1 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -8,16 +8,22 @@ class Clipboard: class Gizmo: + enum Type {SCALE, ROTATE} + var rect : Rect2 var direction := Vector2.ZERO + var type : int - func _init(_direction : Vector2) -> void: + func _init(_type : int = Type.SCALE, _direction := Vector2.ZERO) -> void: + type = _type direction = _direction func get_cursor() -> int: var cursor := Input.CURSOR_MOVE - if direction == Vector2(-1, -1) or direction == Vector2(1, 1): # Top left or bottom right + if direction == Vector2.ZERO: + return Input.CURSOR_POINTING_HAND + elif direction == Vector2(-1, -1) or direction == Vector2(1, 1): # Top left or bottom right cursor = Input.CURSOR_FDIAGSIZE elif direction == Vector2(1, -1) or direction == Vector2(-1, 1): # Top right or bottom left cursor = Input.CURSOR_BDIAGSIZE @@ -41,9 +47,7 @@ var preview_image_texture := ImageTexture.new() var undo_data : Dictionary var drawn_rect := Rect2(0, 0, 0, 0) var gizmos := [] # Array of Gizmos -var rotation_gizmo := Rect2() var dragged_gizmo : Gizmo = null -var is_rotating := false var prev_angle := 0 var mouse_pos_on_gizmo_drag := Vector2.ZERO @@ -51,14 +55,16 @@ onready var marching_ants_outline : Sprite = $MarchingAntsOutline func _ready() -> void: - gizmos.append(Gizmo.new(Vector2(-1, -1))) # Top left - gizmos.append(Gizmo.new(Vector2(0, -1))) # Center top - gizmos.append(Gizmo.new(Vector2(1, -1))) # Top right - gizmos.append(Gizmo.new(Vector2(1, 0))) # Center right - gizmos.append(Gizmo.new(Vector2(1, 1))) # Bottom right - gizmos.append(Gizmo.new(Vector2(0, 1))) # Center bottom - gizmos.append(Gizmo.new(Vector2(-1, 1))) # Bottom left - gizmos.append(Gizmo.new(Vector2(-1, 0))) # Center left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, -1))) # Top left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(0, -1))) # Center top + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, -1))) # Top right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, 0))) # Center right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, 1))) # Bottom right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(0, 1))) # Center bottom + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 1))) # Bottom left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 0))) # Center left + + gizmos.append(Gizmo.new(Gizmo.Type.ROTATE)) # Rotation gizmo (temp) func _input(event : InputEvent) -> void: @@ -70,7 +76,6 @@ func _input(event : InputEvent) -> void: move_content_cancel() elif event is InputEventMouse: var gizmo - var rot_gizmo := rotation_gizmo.has_point(Global.canvas.current_pixel) for g in gizmos: if g.rect.has_point(Global.canvas.current_pixel): gizmo = g @@ -84,38 +89,35 @@ func _input(event : InputEvent) -> void: if event.pressed: if gizmo: Global.has_focus = false - dragged_gizmo = gizmo - mouse_pos_on_gizmo_drag = Global.canvas.current_pixel - temp_rect = big_bounding_rectangle - move_content_start() - marching_ants_outline.offset = Vector2.ZERO - if rot_gizmo: - Global.has_focus = false - is_rotating = true mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + dragged_gizmo = gizmo temp_rect = big_bounding_rectangle move_content_start() - var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) - original_preview_image.crop(img_size, img_size) marching_ants_outline.offset = Vector2.ZERO + if gizmo.type == Gizmo.Type.ROTATE: + var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) + original_preview_image.crop(img_size, img_size) + elif dragged_gizmo: Global.has_focus = true dragged_gizmo = null - elif is_rotating: - Global.has_focus = true - is_rotating = false if dragged_gizmo: - gizmo_resize() - if is_rotating: - gizmo_rotate() + if dragged_gizmo.type == Gizmo.Type.SCALE: + gizmo_resize() + else: + gizmo_rotate() func _big_bounding_rectangle_changed(value : Rect2) -> void: big_bounding_rectangle = value + update_gizmos() + + +func update_gizmos() -> void: var rect_pos : Vector2 = big_bounding_rectangle.position var rect_end : Vector2 = big_bounding_rectangle.end - var size := Vector2.ONE + var size := Vector2.ONE * Global.camera.zoom * 10 # Clockwise, starting from top-left corner gizmos[0].rect = Rect2(rect_pos - size, size) gizmos[1].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) @@ -126,7 +128,9 @@ func _big_bounding_rectangle_changed(value : Rect2) -> void: gizmos[6].rect = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) gizmos[7].rect = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) - rotation_gizmo = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - 2), size) + # Rotation gizmo (temp) + gizmos[8].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - (size.y * 2)), size) + update() func gizmo_resize() -> void: @@ -435,18 +439,11 @@ func _draw() -> void: for gizmo in gizmos: # Draw gizmos draw_rect(gizmo.rect, Color.black) var filled_rect : Rect2 = gizmo.rect - var filled_size := Vector2(0.2, 0.2) + var filled_size : Vector2 = gizmo.rect.size * Vector2(0.2, 0.2) filled_rect.position += filled_size filled_rect.size -= filled_size * 2 draw_rect(filled_rect, Color.white) # Filled white square - draw_rect(rotation_gizmo, Color.black) - var filled_rect : Rect2 = rotation_gizmo - var filled_size := Vector2(0.2, 0.2) - filled_rect.position += filled_size - filled_rect.size -= filled_size * 2 - draw_rect(filled_rect, Color.white) # Filled white square - if is_moving_content and !preview_image.is_empty(): draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) draw_set_transform(position, rotation, scale) From 8f31643350e3182376fb3906214f836008ff068b Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 15:17:28 +0300 Subject: [PATCH 56/69] Remove unneeded parameter from move_bitmap_values() --- src/Classes/Project.gd | 8 +------- src/UI/Canvas/Selection.gd | 10 +++++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index 6929132ce8dd..e8113489f55a 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -654,12 +654,7 @@ func get_selection_rectangle(bitmap : BitMap = selection_bitmap) -> Rect2: return rect -#func selection_is_rectangle(bitmap : BitMap = selection_bitmap) -> bool: -# var selection_rect = get_selection_rectangle(bitmap) -# return selection_rect == Global.canvas.selection.big_bounding_rectangle - - -func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: +func move_bitmap_values(bitmap : BitMap) -> void: var selection_node = Global.canvas.selection var selection_position : Vector2 = selection_node.big_bounding_rectangle.position var selection_end : Vector2 = selection_node.big_bounding_rectangle.end @@ -669,7 +664,6 @@ func move_bitmap_values(bitmap : BitMap, to : Vector2) -> void: var smaller_image := image.get_rect(selection_rect) image.lock() image.fill(Color(0)) - selection_rect.position += to var dst := selection_position var x_diff = selection_end.x - size.x var y_diff = selection_end.y - size.y diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 43c950a770e1..eb662549a200 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -202,9 +202,8 @@ func move_borders(move : Vector2) -> void: func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: marching_ants_outline.offset = Vector2.ZERO - var diff := new_pos - old_pos var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() - Global.current_project.move_bitmap_values(selected_bitmap_copy, diff) + Global.current_project.move_bitmap_values(selected_bitmap_copy) Global.current_project.selection_bitmap = selected_bitmap_copy commit_undo("Rectangle Select", undo_data) @@ -224,14 +223,14 @@ func select_rect(rect : Rect2, select := true) -> void: if offset_position != Vector2.ZERO: big_bounding_rectangle.position -= offset_position - project.move_bitmap_values(selection_bitmap_copy, -offset_position) + project.move_bitmap_values(selection_bitmap_copy) selection_bitmap_copy.set_bit_rect(rect, select) big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) if offset_position != Vector2.ZERO: big_bounding_rectangle.position += offset_position - project.move_bitmap_values(selection_bitmap_copy, offset_position) + project.move_bitmap_values(selection_bitmap_copy) project.selection_bitmap = selection_bitmap_copy self.big_bounding_rectangle = big_bounding_rectangle # call getter method @@ -259,7 +258,7 @@ func move_content_confirm() -> void: var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() - Global.current_project.move_bitmap_values(selected_bitmap_copy, marching_ants_outline.offset) + Global.current_project.move_bitmap_values(selected_bitmap_copy) Global.current_project.selection_bitmap = selected_bitmap_copy original_preview_image = Image.new() @@ -407,6 +406,7 @@ func invert() -> void: project.selection_bitmap = selection_bitmap_copy Global.current_project.selection_bitmap_changed() self.big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + marching_ants_outline.offset = Vector2.ZERO commit_undo("Rectangle Select", _undo_data) From c28e2b46d9a87dbbe885cf8ed16432420f3baeed Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 17:15:45 +0300 Subject: [PATCH 57/69] Remove more unneeded parameters --- src/Tools/RectSelect.gd | 4 ++-- src/UI/Canvas/Selection.gd | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 7b6bc2154ff6..7f7619f12323 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -38,9 +38,9 @@ func draw_move(position : Vector2) -> void: Global.canvas.selection.update() -func draw_end(position : Vector2) -> void: +func draw_end(_position : Vector2) -> void: if _move: - Global.canvas.selection.move_borders_end(position, start_position) + Global.canvas.selection.move_borders_end() else: if !Tools.shift and !Tools.control: Global.canvas.selection.clear_selection() diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index eb662549a200..a76b64d01668 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -200,7 +200,7 @@ func move_borders(move : Vector2) -> void: update() -func move_borders_end(new_pos : Vector2, old_pos : Vector2) -> void: +func move_borders_end() -> void: marching_ants_outline.offset = Vector2.ZERO var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() Global.current_project.move_bitmap_values(selected_bitmap_copy) From d3b7e43a33a30bf94a33f8de19fdfb1cf4e28901 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 18:13:22 +0300 Subject: [PATCH 58/69] Move the selection only if the cursor is above it and neither shift nor control are currently pressed --- src/Tools/RectSelect.gd | 9 +++++---- src/UI/Canvas/Selection.gd | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 7f7619f12323..420edd74aaa8 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -14,16 +14,17 @@ func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_confirm() undo_data = Global.canvas.selection._get_undo_data(false) - if !Global.canvas.selection.big_bounding_rectangle.has_point(position): - _start = Rect2(position, Vector2.ZERO) - - else: + if Global.current_project.selection_bitmap.get_bit(position) and !Tools.control and !Tools.shift: + # Move current selection _move = true _offset = position start_position = position Global.canvas.selection.move_borders_start() _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) + else: + _start = Rect2(position, Vector2.ZERO) + func draw_move(position : Vector2) -> void: if _move: diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index a76b64d01668..d44c488da0ec 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -213,7 +213,7 @@ func move_borders_end() -> void: func select_rect(rect : Rect2, select := true) -> void: var project : Project = Global.current_project var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() - var offset_position := Vector2.ZERO + var offset_position := Vector2.ZERO # Used only if the selection is outside of the canvas boundaries, on the left and/or above (negative coords) if big_bounding_rectangle.position.x < 0: rect.position.x -= big_bounding_rectangle.position.x offset_position.x = big_bounding_rectangle.position.x From a05a927aaa0b866d79d2bc3a0df561baaa7505c3 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 18:22:23 +0300 Subject: [PATCH 59/69] Gradient generation now works on non-rectangular selections Although this behavior might not be the intended one --- src/Autoload/DrawingAlgos.gd | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 96029955622a..6e62a92fec86 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -590,12 +590,13 @@ func generate_gradient(image : Image, colors : Array, steps : int, direction : i if direction == GradientDirection.BOTTOM or direction == GradientDirection.RIGHT: colors.invert() - var selection_rectangle := Rect2() - if affect_selection and project.has_selection: - selection_rectangle = project.get_selection_rectangle() + var draw_rectangle := Rect2() + var selection := affect_selection and project.has_selection + if selection: + draw_rectangle = project.get_selection_rectangle() else: - selection_rectangle = Rect2(Vector2.ZERO, project.size) - var size := selection_rectangle.size + draw_rectangle = Rect2(Vector2.ZERO, project.size) + var size := draw_rectangle.size image.lock() var gradient_size @@ -606,7 +607,9 @@ func generate_gradient(image : Image, colors : Array, steps : int, direction : i var start = i * gradient_size var end = (i + 1) * gradient_size for yy in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + selection_rectangle.position + var pos : Vector2 = Vector2(xx, yy) + draw_rectangle.position + if selection and !project.selection_bitmap.get_bit(pos): + continue image.set_pixelv(pos, colors[i]) else: @@ -616,5 +619,7 @@ func generate_gradient(image : Image, colors : Array, steps : int, direction : i var start = i * gradient_size var end = (i + 1) * gradient_size for xx in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + selection_rectangle.position + var pos : Vector2 = Vector2(xx, yy) + draw_rectangle.position + if selection and !project.selection_bitmap.get_bit(pos): + continue image.set_pixelv(pos, colors[i]) From 0dce088c82de3663f9f149a607061c7d286d1801 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 19:07:55 +0300 Subject: [PATCH 60/69] Copy/paste marching ants effect offset Useful for when the selection is in negative coords --- src/UI/Canvas/Selection.gd | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index d44c488da0ec..c8393c4f62b4 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -5,6 +5,7 @@ class Clipboard: var image := Image.new() var selection_bitmap := BitMap.new() var big_bounding_rectangle := Rect2() + var selection_offset := Vector2.ZERO class Gizmo: @@ -352,12 +353,18 @@ func copy() -> void: for x in to_copy.get_size().x: for y in to_copy.get_size().y: var pos := Vector2(x, y) - if not project.selection_bitmap.get_bit(pos + big_bounding_rectangle.position): + var offset_pos = big_bounding_rectangle.position + if offset_pos.x < 0: + offset_pos.x = 0 + if offset_pos.y < 0: + offset_pos.y = 0 + if not project.selection_bitmap.get_bit(pos + offset_pos): to_copy.set_pixelv(pos, Color(0)) to_copy.unlock() clipboard.image = to_copy clipboard.selection_bitmap = project.selection_bitmap.duplicate() clipboard.big_bounding_rectangle = big_bounding_rectangle + clipboard.selection_offset = marching_ants_outline.offset func paste() -> void: @@ -369,6 +376,7 @@ func paste() -> void: clear_selection() project.selection_bitmap = clipboard.selection_bitmap.duplicate() self.big_bounding_rectangle = clipboard.big_bounding_rectangle + marching_ants_outline.offset = clipboard.selection_offset image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) commit_undo("Draw", _undo_data) From 5b85cbbbe9f08c9a575a13e543675ac2a5a00bdc Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 19:10:12 +0300 Subject: [PATCH 61/69] Fix issue with clear selection & UndoRedo --- src/UI/Canvas/Selection.gd | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index c8393c4f62b4..49d5daad33b7 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -424,9 +424,11 @@ func clear_selection(use_undo := false) -> void: return move_content_confirm() var _undo_data = _get_undo_data(false) - project.selection_bitmap = project.resize_bitmap(project.selection_bitmap, project.size) - var full_rect = Rect2(Vector2.ZERO, project.selection_bitmap.get_size()) - project.selection_bitmap.set_bit_rect(full_rect, false) + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + selection_bitmap_copy = project.resize_bitmap(selection_bitmap_copy, project.size) + var full_rect = Rect2(Vector2.ZERO, selection_bitmap_copy.get_size()) + selection_bitmap_copy.set_bit_rect(full_rect, false) + project.selection_bitmap = selection_bitmap_copy self.big_bounding_rectangle = Rect2() marching_ants_outline.offset = Vector2.ZERO From 510ce88aab00c57d7b9740cd45d571ab649aee1b Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 20:41:07 +0300 Subject: [PATCH 62/69] Restore the ability to move selection when it's in negative coords --- src/Tools/RectSelect.gd | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index 420edd74aaa8..e84f8dce713d 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -13,8 +13,14 @@ var undo_data : Dictionary func draw_start(position : Vector2) -> void: Global.canvas.selection.move_content_confirm() undo_data = Global.canvas.selection._get_undo_data(false) + var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position + var offsetted_pos := position + if selection_position.x < 0: + offsetted_pos.x -= selection_position.x + if selection_position.y < 0: + offsetted_pos.y -= selection_position.y - if Global.current_project.selection_bitmap.get_bit(position) and !Tools.control and !Tools.shift: + if Global.current_project.selection_bitmap.get_bit(offsetted_pos) and !Tools.control and !Tools.shift: # Move current selection _move = true _offset = position From 3f70c1c48952a5f6ad74ca4b679d354aa6ccfb94 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 20:50:22 +0300 Subject: [PATCH 63/69] Made the marching ants offset a Project variable This fixes the issue of project switching and keeping the previous project's offset. Again, this is only relevant for when the selection is in negative coords. --- src/Classes/Project.gd | 25 +++++++++++++++++-------- src/UI/Canvas/Selection.gd | 28 +++++++++++++--------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index e8113489f55a..dc6f8a5e4803 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -23,6 +23,8 @@ var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide var selection_bitmap := BitMap.new() +# This is useful for when the selection is outside of the canvas boundaries, on the left and/or above (negative coords) +var selection_offset := Vector2.ZERO setget _selection_offset_changed var has_selection := false # For every camera (currently there are 3) @@ -97,6 +99,11 @@ func selection_bitmap_changed() -> void: Global.canvas.selection.marching_ants_outline.texture = image_texture +func _selection_offset_changed(value : Vector2) -> void: + selection_offset = value + Global.canvas.selection.marching_ants_outline.offset = selection_offset + + func change_project() -> void: # Remove old nodes for container in Global.layers_container.get_children(): @@ -207,8 +214,10 @@ func change_project() -> void: for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) + Global.canvas.selection.marching_ants_outline.offset = selection_offset selection_bitmap_changed() Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() + Global.canvas.selection.big_bounding_rectangle.position += selection_offset Global.canvas.selection.update() var i := 0 @@ -672,16 +681,16 @@ func move_bitmap_values(bitmap : BitMap) -> void: if selection_position.x < 0: nw -= selection_position.x - selection_node.marching_ants_outline.offset.x = selection_position.x + self.selection_offset.x = selection_position.x dst.x = 0 else: - selection_node.marching_ants_outline.offset.x = 0 + self.selection_offset.x = 0 if selection_position.y < 0: nh -= selection_position.y - selection_node.marching_ants_outline.offset.y = selection_position.y + self.selection_offset.y = selection_position.y dst.y = 0 else: - selection_node.marching_ants_outline.offset.y = 0 + self.selection_offset.y = 0 if nw <= image.get_size().x: nw = image.get_size().x @@ -704,11 +713,11 @@ func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_x : bool, fl var image : Image = bitmap_to_image(bitmap) var selection_rect := image.get_used_rect() var smaller_image := image.get_rect(selection_rect) - if selection_position.x < 0: - selection_node.marching_ants_outline.offset.x = selection_position.x + if selection_position.x <= 0: + self.selection_offset.x = selection_position.x dst.x = 0 - if selection_position.y < 0: - selection_node.marching_ants_outline.offset.y = selection_position.y + if selection_position.y <= 0: + self.selection_offset.y = selection_position.y dst.y = 0 image.lock() image.fill(Color(0)) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index 49d5daad33b7..d02843c7babf 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -94,7 +94,7 @@ func _input(event : InputEvent) -> void: dragged_gizmo = gizmo temp_rect = big_bounding_rectangle move_content_start() - marching_ants_outline.offset = Vector2.ZERO + Global.current_project.selection_offset = Vector2.ZERO if gizmo.type == Gizmo.Type.ROTATE: var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) original_preview_image.crop(img_size, img_size) @@ -202,7 +202,6 @@ func move_borders(move : Vector2) -> void: func move_borders_end() -> void: - marching_ants_outline.offset = Vector2.ZERO var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() Global.current_project.move_bitmap_values(selected_bitmap_copy) @@ -244,7 +243,7 @@ func move_content_start() -> void: get_preview_image() original_bitmap = Global.current_project.selection_bitmap.duplicate() original_big_bounding_rectangle = big_bounding_rectangle - original_offset = marching_ants_outline.offset + original_offset = Global.current_project.selection_offset update() @@ -265,7 +264,6 @@ func move_content_confirm() -> void: original_preview_image = Image.new() preview_image = Image.new() original_bitmap = BitMap.new() -# marching_ants_outline.offset = Vector2.ZERO is_moving_content = false commit_undo("Move Selection", undo_data) update() @@ -274,14 +272,14 @@ func move_content_confirm() -> void: func move_content_cancel() -> void: if preview_image.is_empty(): return - marching_ants_outline.offset = original_offset + var project : Project = Global.current_project + project.selection_offset = original_offset is_moving_content = false self.big_bounding_rectangle = original_big_bounding_rectangle - Global.current_project.selection_bitmap = original_bitmap - Global.current_project.selection_bitmap_changed() + project.selection_bitmap = original_bitmap + project.selection_bitmap_changed() preview_image = original_preview_image - var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) Global.canvas.update_texture(project.current_layer) @@ -299,11 +297,11 @@ func commit_undo(action : String, _undo_data : Dictionary) -> void: project.undo_redo.create_action(action) project.undo_redo.add_do_property(project, "selection_bitmap", redo_data["selection_bitmap"]) project.undo_redo.add_do_property(self, "big_bounding_rectangle", redo_data["big_bounding_rectangle"]) - project.undo_redo.add_do_property(marching_ants_outline, "offset", redo_data["outline_offset"]) + project.undo_redo.add_do_property(project, "selection_offset", redo_data["outline_offset"]) project.undo_redo.add_undo_property(project, "selection_bitmap", _undo_data["selection_bitmap"]) project.undo_redo.add_undo_property(self, "big_bounding_rectangle", _undo_data["big_bounding_rectangle"]) - project.undo_redo.add_undo_property(marching_ants_outline, "offset", _undo_data["outline_offset"]) + project.undo_redo.add_undo_property(project, "selection_offset", _undo_data["outline_offset"]) if "image_data" in _undo_data: @@ -324,7 +322,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: var project := Global.current_project data["selection_bitmap"] = project.selection_bitmap data["big_bounding_rectangle"] = big_bounding_rectangle - data["outline_offset"] = marching_ants_outline.offset + data["outline_offset"] = Global.current_project.selection_offset if undo_image: var image : Image = project.frames[project.current_frame].cels[project.current_layer].image @@ -364,7 +362,7 @@ func copy() -> void: clipboard.image = to_copy clipboard.selection_bitmap = project.selection_bitmap.duplicate() clipboard.big_bounding_rectangle = big_bounding_rectangle - clipboard.selection_offset = marching_ants_outline.offset + clipboard.selection_offset = project.selection_offset func paste() -> void: @@ -376,7 +374,7 @@ func paste() -> void: clear_selection() project.selection_bitmap = clipboard.selection_bitmap.duplicate() self.big_bounding_rectangle = clipboard.big_bounding_rectangle - marching_ants_outline.offset = clipboard.selection_offset + project.selection_offset = clipboard.selection_offset image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) commit_undo("Draw", _undo_data) @@ -414,7 +412,7 @@ func invert() -> void: project.selection_bitmap = selection_bitmap_copy Global.current_project.selection_bitmap_changed() self.big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) - marching_ants_outline.offset = Vector2.ZERO + project.selection_offset = Vector2.ZERO commit_undo("Rectangle Select", _undo_data) @@ -431,7 +429,7 @@ func clear_selection(use_undo := false) -> void: project.selection_bitmap = selection_bitmap_copy self.big_bounding_rectangle = Rect2() - marching_ants_outline.offset = Vector2.ZERO + project.selection_offset = Vector2.ZERO update() if use_undo: commit_undo("Clear Selection", _undo_data) From 8d49f2950178802b018ce37d37268dd6909fb68c Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 22:22:28 +0300 Subject: [PATCH 64/69] Made the "from current selection" palette preset work with the new selection system --- src/Autoload/Palettes.gd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Autoload/Palettes.gd b/src/Autoload/Palettes.gd index d3c986286598..30bdca114326 100644 --- a/src/Autoload/Palettes.gd +++ b/src/Autoload/Palettes.gd @@ -124,7 +124,12 @@ func create_new_palette_from_current_palette(name: String, comment: String) -> v func create_new_palette_from_current_selection(name: String, comment: String, width: int, height: int, add_alpha_colors: bool, get_colors_from: int): var new_palette: Palette = Palette.new(name, width, height, comment) var current_project = Global.current_project - var pixels = current_project.selected_pixels.duplicate() + var pixels := [] + for x in current_project.size.x: + for y in current_project.size.y: + var pos := Vector2(x, y) + if current_project.selection_bitmap.get_bit(pos): + pixels.append(pos) fill_new_palette_with_colors(pixels, new_palette, add_alpha_colors, get_colors_from) From 8513fcaef605d762b9b4758339ca48974af59e1e Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Fri, 16 Apr 2021 22:42:32 +0300 Subject: [PATCH 65/69] Fix out of bounds error when using the rectangular select tool on negative coords --- src/Tools/RectSelect.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index e84f8dce713d..d40324f6e338 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -20,7 +20,7 @@ func draw_start(position : Vector2) -> void: if selection_position.y < 0: offsetted_pos.y -= selection_position.y - if Global.current_project.selection_bitmap.get_bit(offsetted_pos) and !Tools.control and !Tools.shift: + if offsetted_pos.x >= 0 and offsetted_pos.y >= 0 and Global.current_project.selection_bitmap.get_bit(offsetted_pos) and !Tools.control and !Tools.shift: # Move current selection _move = true _offset = position From 2af5e1bff19ea9e0303bdf2759311e31689b9f20 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 17 Apr 2021 00:01:30 +0300 Subject: [PATCH 66/69] Some code cleanup --- src/Classes/Project.gd | 5 +---- src/Tools/Move.gd | 35 ++++++++++++------------------- src/Tools/RectSelect.gd | 46 +++++++++++++---------------------------- 3 files changed, 28 insertions(+), 58 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index dc6f8a5e4803..fee9da55e8bd 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -160,9 +160,6 @@ func change_project() -> void: self.animation_tags = animation_tags - # Change the selection rectangle -# Global.selection_rectangl.set_rect(selected_rect) - # Change the guides for guide in Global.canvas.get_children(): if guide is Guide: @@ -214,6 +211,7 @@ func change_project() -> void: for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) + # Change selection effect & bounding rectangle Global.canvas.selection.marching_ants_outline.offset = selection_offset selection_bitmap_changed() Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() @@ -381,7 +379,6 @@ func name_changed(value : String) -> void: func size_changed(value : Vector2) -> void: size = value update_tile_mode_rects() -# Global.selection_rectangl.set_rect(Global.selection_rectangl.get_rect()) func frames_changed(value : Array) -> void: diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd index b3048f6dfbc4..e1cfd464a1dc 100644 --- a/src/Tools/Move.gd +++ b/src/Tools/Move.gd @@ -1,49 +1,40 @@ extends BaseTool -var starting_pos : Vector2 -var offset : Vector2 +var _starting_pos : Vector2 +var _offset : Vector2 func draw_start(position : Vector2) -> void: - starting_pos = position - offset = position + _starting_pos = position + _offset = position if Global.current_project.has_selection: Global.canvas.selection.move_content_start() func draw_move(position : Vector2) -> void: if Global.current_project.has_selection: - Global.canvas.selection.move_content(position - offset) - offset = position + Global.canvas.selection.move_content(position - _offset) + _offset = position else: - Global.canvas.move_preview_location = position - starting_pos - offset = position + Global.canvas.move_preview_location = position - _starting_pos + _offset = position func draw_end(position : Vector2) -> void: - if starting_pos != Vector2.INF: - var pixel_diff : Vector2 = position - starting_pos -# if pixel_diff != Vector2.ZERO: + if _starting_pos != Vector2.INF: + var pixel_diff : Vector2 = position - _starting_pos var project : Project = Global.current_project var image : Image = _get_draw_image() - if project.has_selection: - pass -# Global.canvas.selection.move_content_end() -# Global.canvas.selection.move_borders_end(position, starting_pos) - else: + if !project.has_selection: Global.canvas.move_preview_location = Vector2.ZERO var image_copy := Image.new() image_copy.copy_from(image) Global.canvas.handle_undo("Draw") image.fill(Color(0, 0, 0, 0)) -# image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) -# for pixel in pixels: -## image.set_pixelv(pixel[0] + pixel_diff, Color.red) -# image.set_pixelv(pixel[0] + pixel_diff, pixel[1]) + Global.canvas.handle_redo("Draw") - print(pixel_diff) - starting_pos = Vector2.INF + _starting_pos = Vector2.INF diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index d40324f6e338..18c5bbda9431 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -1,11 +1,9 @@ extends BaseTool -var start_position := Vector2.INF -var rect := Rect2(0, 0, 0, 0) +var _rect := Rect2(0, 0, 0, 0) var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO -var _drag := false var _move := false var undo_data : Dictionary @@ -24,9 +22,7 @@ func draw_start(position : Vector2) -> void: # Move current selection _move = true _offset = position - start_position = position Global.canvas.selection.move_borders_start() - _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) else: _start = Rect2(position, Vector2.ZERO) @@ -38,10 +34,10 @@ func draw_move(position : Vector2) -> void: _offset = position _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) else: - rect = _start.expand(position).abs() - rect = rect.grow_individual(0, 0, 1, 1) - _set_cursor_text(rect) - Global.canvas.selection.drawn_rect = rect + _rect = _start.expand(position).abs() + _rect = _rect.grow_individual(0, 0, 1, 1) + _set_cursor_text(_rect) + Global.canvas.selection.drawn_rect = _rect Global.canvas.selection.update() @@ -51,34 +47,20 @@ func draw_end(_position : Vector2) -> void: else: if !Tools.shift and !Tools.control: Global.canvas.selection.clear_selection() - if rect.size == Vector2.ZERO and Global.current_project.has_selection: + if _rect.size == Vector2.ZERO and Global.current_project.has_selection: Global.canvas.selection.commit_undo("Rectangle Select", undo_data) - if rect.size != Vector2.ZERO: - Global.canvas.selection.select_rect(rect, !Tools.control) + if _rect.size != Vector2.ZERO: + Global.canvas.selection.select_rect(_rect, !Tools.control) Global.canvas.selection.commit_undo("Rectangle Select", undo_data) _move = false cursor_text = "" - start_position = Vector2.INF - rect = Rect2(0, 0, 0, 0) - Global.canvas.selection.drawn_rect = rect + _rect = Rect2(0, 0, 0, 0) + Global.canvas.selection.drawn_rect = _rect Global.canvas.selection.update() -func cursor_move(_position : Vector2) -> void: - pass -# if _drag: -# _cursor = Vector2.INF -# elif Global.selection_rectangle.has_point(position): -# _cursor = Vector2.INF -# Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_MOVE -# Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) -# else: -# _cursor = position -# Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS - - -func _set_cursor_text(_rect : Rect2) -> void: - cursor_text = "%s, %s" % [_rect.position.x, _rect.position.y] - cursor_text += " -> %s, %s" % [_rect.end.x - 1, _rect.end.y - 1] - cursor_text += " (%s, %s)" % [_rect.size.x, _rect.size.y] +func _set_cursor_text(rect : Rect2) -> void: + cursor_text = "%s, %s" % [rect.position.x, rect.position.y] + cursor_text += " -> %s, %s" % [rect.end.x - 1, rect.end.y - 1] + cursor_text += " (%s, %s)" % [rect.size.x, rect.size.y] From 3bd78326c6a0d584de5b79776f8036303bb99a73 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 17 Apr 2021 00:12:20 +0300 Subject: [PATCH 67/69] Comment out the rotation gizmo for now, since it does not work properly --- src/UI/Canvas/Selection.gd | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index d02843c7babf..cef2cda41046 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -65,18 +65,18 @@ func _ready() -> void: gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 1))) # Bottom left gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 0))) # Center left - gizmos.append(Gizmo.new(Gizmo.Type.ROTATE)) # Rotation gizmo (temp) +# gizmos.append(Gizmo.new(Gizmo.Type.ROTATE)) # Rotation gizmo (temp) func _input(event : InputEvent) -> void: if event is InputEventKey: if is_moving_content: # Temporary code - if event.scancode == 16777221: + if event.scancode == 16777221: # Enter move_content_confirm() - elif event.scancode == 16777217: + elif event.scancode == 16777217: # Escape move_content_cancel() elif event is InputEventMouse: - var gizmo + var gizmo : Gizmo for g in gizmos: if g.rect.has_point(Global.canvas.current_pixel): gizmo = g @@ -130,7 +130,7 @@ func update_gizmos() -> void: gizmos[7].rect = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) # Rotation gizmo (temp) - gizmos[8].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - (size.y * 2)), size) +# gizmos[8].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - (size.y * 2)), size) update() @@ -160,13 +160,12 @@ func gizmo_resize() -> void: update() -func gizmo_rotate() -> void: +func gizmo_rotate() -> void: # Does not work properly yet var angle := Global.canvas.current_pixel.angle_to_point(mouse_pos_on_gizmo_drag) angle = deg2rad(floor(rad2deg(angle))) if angle == prev_angle: return prev_angle = angle -# print(rad2deg(angle)) # var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) # warning-ignore:integer_division # warning-ignore:integer_division @@ -187,7 +186,6 @@ func gizmo_rotate() -> void: Global.current_project.selection_bitmap.create_from_image_alpha(bitmap_image) Global.current_project.selection_bitmap_changed() self.big_bounding_rectangle = bitmap_image.get_used_rect() -# print(big_bounding_rectangle) update() @@ -329,8 +327,7 @@ func _get_undo_data(undo_image : bool) -> Dictionary: image.unlock() data["image_data"] = image.data image.lock() -# for d in data.keys(): -# print(d, data[d]) + return data From 69019e9b7a4700c73a1eaa93e0e3e1a704419297 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 17 Apr 2021 02:58:20 +0300 Subject: [PATCH 68/69] Update marching ants shader params and gizmo sizes when the bitmap changes --- src/Classes/Project.gd | 2 ++ src/UI/Canvas/CameraMovement.gd | 9 +-------- src/UI/Canvas/Selection.gd | 10 ++++++++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index fee9da55e8bd..ca0b6b1a4451 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -102,6 +102,7 @@ func selection_bitmap_changed() -> void: func _selection_offset_changed(value : Vector2) -> void: selection_offset = value Global.canvas.selection.marching_ants_outline.offset = selection_offset + Global.canvas.selection.update_on_zoom(Global.camera.zoom.x) func change_project() -> void: @@ -727,4 +728,5 @@ func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_x : bool, fl image.crop(new_bitmap_size.x, new_bitmap_size.y) image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), dst) new_bitmap.create_from_image_alpha(image) + return new_bitmap diff --git a/src/UI/Canvas/CameraMovement.gd b/src/UI/Canvas/CameraMovement.gd index bdc19602f1cc..090a8dfc8e09 100644 --- a/src/UI/Canvas/CameraMovement.gd +++ b/src/UI/Canvas/CameraMovement.gd @@ -179,14 +179,7 @@ func zoom_changed() -> void: for guide in Global.current_project.guides: guide.width = zoom.x * 2 - var marching_ants : Sprite = Global.canvas.selection.marching_ants_outline - var size := max(Global.current_project.selection_bitmap.get_size().x, Global.current_project.selection_bitmap.get_size().y) - marching_ants.material.set_shader_param("width", zoom.x) - marching_ants.material.set_shader_param("frequency", (1.0 / zoom.x) * 10 * size / 64) - for gizmo in Global.canvas.selection.gizmos: - if gizmo.rect.size == Vector2.ZERO: - return - Global.canvas.selection.update_gizmos() + Global.canvas.selection.update_on_zoom(zoom.x) elif name == "CameraPreview": Global.preview_zoom_slider.value = -zoom.x diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index cef2cda41046..f4e49d285524 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -134,6 +134,16 @@ func update_gizmos() -> void: update() +func update_on_zoom(zoom : float) -> void: + var size := max(Global.current_project.selection_bitmap.get_size().x, Global.current_project.selection_bitmap.get_size().y) + marching_ants_outline.material.set_shader_param("width", zoom) + marching_ants_outline.material.set_shader_param("frequency", (1.0 / zoom) * 10 * size / 64) + for gizmo in gizmos: + if gizmo.rect.size == Vector2.ZERO: + return + update_gizmos() + + func gizmo_resize() -> void: var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction var dir := dragged_gizmo.direction From 8723e4db1494fc7559d2f656a4ac1c00fc34395a Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 17 Apr 2021 03:02:40 +0300 Subject: [PATCH 69/69] Move some methods around in Selection.gd --- src/UI/Canvas/Selection.gd | 83 +++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd index f4e49d285524..469f6eeb41e9 100644 --- a/src/UI/Canvas/Selection.gd +++ b/src/UI/Canvas/Selection.gd @@ -110,6 +110,28 @@ func _input(event : InputEvent) -> void: gizmo_rotate() +func _draw() -> void: + var _position := position + var _scale := scale + if Global.mirror_view: + _position.x = _position.x + Global.current_project.size.x + _scale.x = -1 + draw_set_transform(_position, rotation, _scale) + draw_rect(drawn_rect, Color.black, false) + if big_bounding_rectangle.size != Vector2.ZERO: + for gizmo in gizmos: # Draw gizmos + draw_rect(gizmo.rect, Color.black) + var filled_rect : Rect2 = gizmo.rect + var filled_size : Vector2 = gizmo.rect.size * Vector2(0.2, 0.2) + filled_rect.position += filled_size + filled_rect.size -= filled_size * 2 + draw_rect(filled_rect, Color.white) # Filled white square + + if is_moving_content and !preview_image.is_empty(): + draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) + draw_set_transform(position, rotation, scale) + + func _big_bounding_rectangle_changed(value : Rect2) -> void: big_bounding_rectangle = value update_gizmos() @@ -199,25 +221,6 @@ func gizmo_rotate() -> void: # Does not work properly yet update() -func move_borders_start() -> void: - undo_data = _get_undo_data(false) - - -func move_borders(move : Vector2) -> void: - marching_ants_outline.offset += move - self.big_bounding_rectangle.position += move - update() - - -func move_borders_end() -> void: - var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() - Global.current_project.move_bitmap_values(selected_bitmap_copy) - - Global.current_project.selection_bitmap = selected_bitmap_copy - commit_undo("Rectangle Select", undo_data) - update() - - func select_rect(rect : Rect2, select := true) -> void: var project : Project = Global.current_project var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() @@ -244,6 +247,26 @@ func select_rect(rect : Rect2, select := true) -> void: self.big_bounding_rectangle = big_bounding_rectangle # call getter method + +func move_borders_start() -> void: + undo_data = _get_undo_data(false) + + +func move_borders(move : Vector2) -> void: + marching_ants_outline.offset += move + self.big_bounding_rectangle.position += move + update() + + +func move_borders_end() -> void: + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy) + + Global.current_project.selection_bitmap = selected_bitmap_copy + commit_undo("Rectangle Select", undo_data) + update() + + func move_content_start() -> void: if !is_moving_content: is_moving_content = true @@ -442,28 +465,6 @@ func clear_selection(use_undo := false) -> void: commit_undo("Clear Selection", _undo_data) -func _draw() -> void: - var _position := position - var _scale := scale - if Global.mirror_view: - _position.x = _position.x + Global.current_project.size.x - _scale.x = -1 - draw_set_transform(_position, rotation, _scale) - draw_rect(drawn_rect, Color.black, false) - if big_bounding_rectangle.size != Vector2.ZERO: - for gizmo in gizmos: # Draw gizmos - draw_rect(gizmo.rect, Color.black) - var filled_rect : Rect2 = gizmo.rect - var filled_size : Vector2 = gizmo.rect.size * Vector2(0.2, 0.2) - filled_rect.position += filled_size - filled_rect.size -= filled_size * 2 - draw_rect(filled_rect, Color.white) # Filled white square - - if is_moving_content and !preview_image.is_empty(): - draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) - draw_set_transform(position, rotation, scale) - - func get_preview_image() -> void: var project : Project = Global.current_project var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image