From f3fb98e06831d3d2454617d362aab460814ea466 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] 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 | 12 +- 10 files changed, 230 insertions(+), 74 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 2571f75..8af33c3 100644 --- a/project.godot +++ b/project.godot @@ -84,6 +84,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", @@ -105,6 +115,8 @@ _global_script_class_icons={ "PaletteColor": "", "Patterns": "", "Project": "", +"Selection": "", +"SelectionShape": "", "SymmetryGuide": "" } diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 7fcdf47..c51dd2d 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -111,7 +111,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 @@ -215,7 +214,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 4d7a57e..7d25d25 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(): @@ -364,7 +363,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: @@ -583,3 +582,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 0000000..1652ba4 --- /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 105b30f..f0ef641 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 e4f10c8..e22dad7 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 8c76881..7fa4c2c 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() - - 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)) +# visible = not rect.has_no_area() -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 0000000..20417e4 --- /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 2be69a4..0309a25 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -257,16 +257,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 df42c27..ecea63b 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=28 format=2] +[gd_scene load_steps=27 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] @@ -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] @@ -348,15 +347,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