1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-07 19:09:50 +00:00
Pixelorama/src/Tools/BaseTool.gd
Emmanouil Papadeas 42d6f12530 Implement rectangular grid and guide snapping
Snap to the rectangular grid and guides (excluding symmetry guides). Can be toggled from the View menu. Currently affects the following tools: Pencil, Eraser, Shading, Line, all shape tool and all selection tools. Although maybe this should not affect the Magic Wand and Select By Color? The snapping distance is currently unaffected by the zoom (should change), and it will be exposed as a parameter in the preferences at a later commit.
2023-01-25 04:37:03 +02:00

243 lines
7.1 KiB
GDScript

class_name BaseTool
extends VBoxContainer
var is_moving = false
var kname: String
var tool_slot = null # Tools.Slot, can't have static typing due to cyclic errors
var cursor_text := ""
var _cursor := Vector2.INF
var _draw_cache: PoolVector2Array = [] # for storing already drawn pixels
var _for_frame := 0 # cache for which frame?
onready var color_rect: ColorRect = $ColorRect
func _ready() -> void:
kname = name.replace(" ", "_").to_lower()
if tool_slot.name == "Left tool":
color_rect.color = Global.left_tool_color
else:
color_rect.color = Global.right_tool_color
$Label.text = Tools.tools[name].display_name
load_config()
func save_config() -> void:
var config := get_config()
Global.config_cache.set_value(tool_slot.kname, kname, config)
func load_config() -> void:
var value = Global.config_cache.get_value(tool_slot.kname, kname, {})
set_config(value)
update_config()
func get_config() -> Dictionary:
return {}
func set_config(_config: Dictionary) -> void:
pass
func update_config() -> void:
pass
func draw_start(_position: Vector2) -> void:
_draw_cache = []
is_moving = true
Global.current_project.can_undo = false
func draw_move(position: Vector2) -> void:
# This can happen if the user switches between tools with a shortcut
# while using another tool
if !is_moving:
draw_start(position)
func draw_end(_position: Vector2) -> void:
is_moving = false
_draw_cache = []
Global.current_project.can_undo = true
func cursor_move(position: Vector2) -> void:
_cursor = position
func draw_indicator(left: bool) -> void:
var rect := Rect2(_cursor, Vector2.ONE)
var color := Global.left_tool_color if left else Global.right_tool_color
Global.canvas.indicators.draw_rect(rect, color, false)
func draw_preview() -> void:
pass
func snap_position(position: Vector2) -> Vector2:
var snap_distance := Global.snapping_distance * Vector2.ONE
if Global.snap_to_rectangular_grid:
var grid_size := Vector2(Global.grid_width, Global.grid_height)
var grid_offset := Vector2(Global.grid_offset_x, Global.grid_offset_y)
var grid_pos := position.snapped(grid_size)
grid_pos += grid_offset
var closest_point_grid := _get_closest_point_to_grid(position, snap_distance, grid_pos)
if closest_point_grid != Vector2.INF:
position = closest_point_grid.floor()
if Global.snap_to_guides:
var snap_to := Vector2.INF
for guide in Global.current_project.guides:
if guide is SymmetryGuide:
continue
var closest_point := _get_closest_point_to_segment(
position, snap_distance, guide.points[0], guide.points[1]
)
if closest_point == Vector2.INF: # Is not close to a guide
continue
# Snap to the closest guide
if (
snap_to == Vector2.INF
or (snap_to - position).length() > (closest_point - position).length()
):
snap_to = closest_point
if snap_to != Vector2.INF:
position = snap_to.floor()
return position
func _get_closest_point_to_grid(
position: Vector2, snap_distance: Vector2, grid_pos: Vector2
) -> Vector2:
# If the cursor is close to the start/origin of a grid cell, snap to that
var closest_point := Vector2.INF
var rect := Rect2()
rect.position = position - (snap_distance / 4.0)
rect.end = position + (snap_distance / 4.0)
if rect.has_point(grid_pos):
closest_point = grid_pos
return closest_point
# If the cursor is far from the grid cell origin but still close to a grid line
# Look for a point close to a horizontal grid line
var grid_start_hor := Vector2(0, grid_pos.y)
var grid_end_hor := Vector2(Global.current_project.size.x, grid_pos.y)
var closest_point_hor := _get_closest_point_to_segment(
position, snap_distance, grid_start_hor, grid_end_hor
)
# Look for a point close to a vertical grid line
var grid_start_ver := Vector2(grid_pos.x, 0)
var grid_end_ver := Vector2(grid_pos.x, Global.current_project.size.y)
var closest_point_ver := _get_closest_point_to_segment(
position, snap_distance, grid_start_ver, grid_end_ver
)
# Snap to the closest point to the closest grid line
var horizontal_distance := (closest_point_hor - position).length()
var vertical_distance := (closest_point_ver - position).length()
if horizontal_distance < vertical_distance:
closest_point = closest_point_hor
elif horizontal_distance > vertical_distance:
closest_point = closest_point_ver
elif horizontal_distance == vertical_distance and closest_point_hor != Vector2.INF:
closest_point = grid_pos
return closest_point
func _get_closest_point_to_segment(
position: Vector2, distance: Vector2, s1: Vector2, s2: Vector2
) -> Vector2:
var closest_point := Vector2.INF
if Geometry.segment_intersects_segment_2d(position - distance, position + distance, s1, s2):
closest_point = Geometry.get_closest_point_to_segment_2d(position, s1, s2)
return closest_point
func _get_draw_rect() -> Rect2:
if Global.current_project.has_selection:
return Global.current_project.selection_map.get_used_rect()
else:
return Rect2(Vector2.ZERO, Global.current_project.size)
func _get_draw_image() -> Image:
return Global.current_project.get_current_cel().get_image()
func _get_selected_draw_images() -> Array: # Array of Images
var images := []
var project: Project = Global.current_project
for cel_index in project.selected_cels:
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
if project.layers[cel_index[1]].can_layer_get_drawn():
images.append(cel.image)
return images
func _flip_rect(rect: Rect2, size: Vector2, horizontal: bool, vertical: bool) -> Rect2:
var result := rect
if horizontal:
result.position.x = size.x - rect.end.x
result.end.x = size.x - rect.position.x
if vertical:
result.position.y = size.y - rect.end.y
result.end.y = size.y - rect.position.y
return result.abs()
func _create_polylines(bitmap: BitMap) -> Array:
var lines := []
var size := bitmap.get_size()
for y in size.y:
for x in size.x:
var p := Vector2(x, y)
if not bitmap.get_bit(p):
continue
if x <= 0 or not bitmap.get_bit(p - Vector2(1, 0)):
_add_polylines_segment(lines, p, p + Vector2(0, 1))
if y <= 0 or not bitmap.get_bit(p - Vector2(0, 1)):
_add_polylines_segment(lines, p, p + Vector2(1, 0))
if x + 1 >= size.x or not bitmap.get_bit(p + Vector2(1, 0)):
_add_polylines_segment(lines, p + Vector2(1, 0), p + Vector2(1, 1))
if y + 1 >= size.y or not bitmap.get_bit(p + Vector2(0, 1)):
_add_polylines_segment(lines, p + Vector2(0, 1), p + Vector2(1, 1))
return lines
func _fill_bitmap_with_points(points: Array, size: Vector2) -> BitMap:
var bitmap := BitMap.new()
bitmap.create(size)
for point in points:
if point.x < 0 or point.y < 0 or point.x >= size.x or point.y >= size.y:
continue
bitmap.set_bit(point, 1)
return bitmap
func _add_polylines_segment(lines: Array, start: Vector2, end: Vector2) -> void:
for line in lines:
if line[0] == start:
line.insert(0, end)
return
if line[0] == end:
line.insert(0, start)
return
if line[line.size() - 1] == start:
line.append(end)
return
if line[line.size() - 1] == end:
line.append(start)
return
lines.append([start, end])
func _exit_tree() -> void:
if is_moving:
draw_end(Global.canvas.current_pixel.floor())