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.
This commit is contained in:
Manolis Papadeas 2021-02-13 16:56:21 +02:00
parent 2a086e3ea0
commit f3fb98e068
10 changed files with 230 additions and 74 deletions

View file

@ -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": ""
}

View file

@ -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")

View file

@ -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

11
src/Classes/Selection.gd Normal file
View file

@ -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)

View file

@ -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()

View file

@ -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:

View file

@ -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()
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() -> void:
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):
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
project.selected_pixels.append(Vector2(x, y))
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 move_rect(move : Vector2) -> void:
_selected_rect.position += move
_clipped_rect.position += move
set_rect(_selected_rect)
func select_rect() -> void:
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 = []

View file

@ -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"]

View file

@ -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)

View file

@ -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