mirror of
https://github.com/tonytins/CozyPixelStudio.git
synced 2025-05-05 18:44:50 -04:00
Refactoring tools (#281)
* Refactoring tools * Remove unused code * Fixed some inferring errors and added translations * Attempt to fix some Script Errors found in the CI workflow * Fix bucket crash. * Fix static type convert. Co-authored-by: OverloadedOrama <35376950+OverloadedOrama@users.noreply.github.com>
This commit is contained in:
parent
e1724148fc
commit
4a668f71f5
42 changed files with 2489 additions and 2389 deletions
|
@ -97,6 +97,15 @@ msgstr ""
|
||||||
msgid "Redo"
|
msgid "Redo"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Copy"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Paste"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Scale Image"
|
msgid "Scale Image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,11 @@ _global_script_classes=[ {
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
"path": "res://src/Classes/AnimationTag.gd"
|
"path": "res://src/Classes/AnimationTag.gd"
|
||||||
}, {
|
}, {
|
||||||
|
"base": "Popup",
|
||||||
|
"class": "Brushes",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://src/UI/BrushesPopup.gd"
|
||||||
|
}, {
|
||||||
"base": "Node2D",
|
"base": "Node2D",
|
||||||
"class": "Canvas",
|
"class": "Canvas",
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
|
@ -59,6 +64,11 @@ _global_script_classes=[ {
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
"path": "res://src/Palette/PaletteColor.gd"
|
"path": "res://src/Palette/PaletteColor.gd"
|
||||||
}, {
|
}, {
|
||||||
|
"base": "PopupPanel",
|
||||||
|
"class": "Patterns",
|
||||||
|
"language": "GDScript",
|
||||||
|
"path": "res://src/UI/PatternsPopup.gd"
|
||||||
|
}, {
|
||||||
"base": "Reference",
|
"base": "Reference",
|
||||||
"class": "Project",
|
"class": "Project",
|
||||||
"language": "GDScript",
|
"language": "GDScript",
|
||||||
|
@ -66,6 +76,7 @@ _global_script_classes=[ {
|
||||||
} ]
|
} ]
|
||||||
_global_script_class_icons={
|
_global_script_class_icons={
|
||||||
"AnimationTag": "",
|
"AnimationTag": "",
|
||||||
|
"Brushes": "",
|
||||||
"Canvas": "",
|
"Canvas": "",
|
||||||
"Cel": "",
|
"Cel": "",
|
||||||
"Drawer": "",
|
"Drawer": "",
|
||||||
|
@ -75,6 +86,7 @@ _global_script_class_icons={
|
||||||
"LayerButton": "",
|
"LayerButton": "",
|
||||||
"Palette": "",
|
"Palette": "",
|
||||||
"PaletteColor": "",
|
"PaletteColor": "",
|
||||||
|
"Patterns": "",
|
||||||
"Project": ""
|
"Project": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +109,7 @@ Global="*res://src/Autoload/Global.gd"
|
||||||
Import="*res://src/Autoload/Import.gd"
|
Import="*res://src/Autoload/Import.gd"
|
||||||
OpenSave="*res://src/Autoload/OpenSave.gd"
|
OpenSave="*res://src/Autoload/OpenSave.gd"
|
||||||
DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd"
|
DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd"
|
||||||
|
Tools="*res://src/Autoload/Tools.gd"
|
||||||
Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd"
|
Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd"
|
||||||
|
|
||||||
[debug]
|
[debug]
|
||||||
|
|
|
@ -1,336 +1,6 @@
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
var drawer := Drawer.new()
|
|
||||||
var mouse_press_pixels := [] # Cleared after mouse release
|
|
||||||
var mouse_press_pressure_values := [] # Cleared after mouse release
|
|
||||||
|
|
||||||
|
|
||||||
func reset() -> void:
|
|
||||||
drawer.reset()
|
|
||||||
mouse_press_pixels.clear()
|
|
||||||
mouse_press_pressure_values.clear()
|
|
||||||
|
|
||||||
|
|
||||||
func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color, pen_pressure : float, current_mouse_button := -1, current_action := -1) -> void:
|
|
||||||
var x_min = Global.current_project.x_min
|
|
||||||
var x_max = Global.current_project.x_max
|
|
||||||
var y_min = Global.current_project.y_min
|
|
||||||
var y_max = Global.current_project.y_max
|
|
||||||
|
|
||||||
# Check if Tiling is enabled and whether mouse is in TilingPreviews
|
|
||||||
if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
|
|
||||||
pos = pos.posmodv(Global.current_project.size)
|
|
||||||
|
|
||||||
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
|
|
||||||
return
|
|
||||||
|
|
||||||
var pos_floored := pos.floor()
|
|
||||||
var current_pixel_color = sprite.get_pixelv(pos)
|
|
||||||
var saved_pixel_index := mouse_press_pixels.find(pos_floored)
|
|
||||||
if current_action == Global.Tools.PENCIL && color.a < 1:
|
|
||||||
color = blend_colors(color, current_pixel_color)
|
|
||||||
|
|
||||||
if current_pixel_color != color && (saved_pixel_index == -1 || pen_pressure > mouse_press_pressure_values[saved_pixel_index]):
|
|
||||||
if current_action == Global.Tools.LIGHTENDARKEN:
|
|
||||||
var ld : int = Global.ld_modes[current_mouse_button]
|
|
||||||
var ld_amount : float = Global.ld_amounts[current_mouse_button]
|
|
||||||
if ld == Global.Lighten_Darken_Mode.LIGHTEN:
|
|
||||||
color = current_pixel_color.lightened(ld_amount)
|
|
||||||
else:
|
|
||||||
color = current_pixel_color.darkened(ld_amount)
|
|
||||||
|
|
||||||
if saved_pixel_index == -1:
|
|
||||||
mouse_press_pixels.append(pos_floored)
|
|
||||||
mouse_press_pressure_values.append(pen_pressure)
|
|
||||||
else:
|
|
||||||
mouse_press_pressure_values[saved_pixel_index] = pen_pressure
|
|
||||||
drawer.set_pixel(sprite, pos, color)
|
|
||||||
|
|
||||||
|
|
||||||
func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_button : int, pen_pressure : float, current_action := -1) -> void:
|
|
||||||
if Global.can_draw && Global.has_focus:
|
|
||||||
var x_min = Global.current_project.x_min
|
|
||||||
var x_max = Global.current_project.x_max
|
|
||||||
var y_min = Global.current_project.y_min
|
|
||||||
var y_max = Global.current_project.y_max
|
|
||||||
|
|
||||||
if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.ALPHA:
|
|
||||||
if current_action == Global.Tools.PENCIL:
|
|
||||||
color.a *= pen_pressure
|
|
||||||
elif current_action == Global.Tools.ERASER: # This is not working
|
|
||||||
color.a *= (1.0 - pen_pressure)
|
|
||||||
|
|
||||||
var brush_size : int = Global.brush_sizes[current_mouse_button]
|
|
||||||
var brush_type : int = Global.current_brush_types[current_mouse_button]
|
|
||||||
|
|
||||||
var horizontal_mirror : bool = Global.horizontal_mirror[current_mouse_button]
|
|
||||||
var vertical_mirror : bool = Global.vertical_mirror[current_mouse_button]
|
|
||||||
var pixel_perfect : bool = Global.pixel_perfect[current_mouse_button]
|
|
||||||
|
|
||||||
drawer.pixel_perfect = pixel_perfect if brush_size == 1 else false
|
|
||||||
drawer.h_mirror = horizontal_mirror
|
|
||||||
drawer.v_mirror = vertical_mirror
|
|
||||||
|
|
||||||
if brush_type == Global.Brush_Types.PIXEL || current_action == Global.Tools.LIGHTENDARKEN:
|
|
||||||
var start_pos_x = floor(pos.x - (brush_size >> 1))
|
|
||||||
var start_pos_y = floor(pos.y - (brush_size >> 1))
|
|
||||||
var end_pos_x = floor(start_pos_x + brush_size)
|
|
||||||
var end_pos_y = floor(start_pos_y + brush_size)
|
|
||||||
|
|
||||||
for cur_pos_x in range(start_pos_x, end_pos_x):
|
|
||||||
for cur_pos_y in range(start_pos_y, end_pos_y):
|
|
||||||
draw_pixel_blended(sprite, Vector2(cur_pos_x, cur_pos_y), color, pen_pressure, current_mouse_button, current_action)
|
|
||||||
Global.canvas.sprite_changed_this_frame = true
|
|
||||||
|
|
||||||
elif brush_type == Global.Brush_Types.CIRCLE || brush_type == Global.Brush_Types.FILLED_CIRCLE:
|
|
||||||
plot_circle(sprite, pos.x, pos.y, brush_size, color, pen_pressure, brush_type == Global.Brush_Types.FILLED_CIRCLE)
|
|
||||||
Global.canvas.sprite_changed_this_frame = true
|
|
||||||
|
|
||||||
else:
|
|
||||||
var brush_index : int = Global.custom_brush_indexes[current_mouse_button]
|
|
||||||
var custom_brush_image : Image
|
|
||||||
if brush_type != Global.Brush_Types.RANDOM_FILE:
|
|
||||||
custom_brush_image = Global.brush_images[current_mouse_button]
|
|
||||||
else: # Handle random brush
|
|
||||||
var brush_button = Global.file_brush_container.get_child(brush_index + 3)
|
|
||||||
var random_index = randi() % brush_button.random_brushes.size()
|
|
||||||
custom_brush_image = Image.new()
|
|
||||||
custom_brush_image.copy_from(brush_button.random_brushes[random_index])
|
|
||||||
var custom_brush_size = custom_brush_image.get_size()
|
|
||||||
custom_brush_image.resize(custom_brush_size.x * brush_size, custom_brush_size.y * brush_size, Image.INTERPOLATE_NEAREST)
|
|
||||||
custom_brush_image = Global.blend_image_with_color(custom_brush_image, color, Global.interpolate_spinboxes[current_mouse_button].value / 100)
|
|
||||||
custom_brush_image.lock()
|
|
||||||
|
|
||||||
var custom_brush_size := custom_brush_image.get_size() - Vector2.ONE
|
|
||||||
pos = pos.floor()
|
|
||||||
# #Check if Tiling is enabled and whether mouse is in TilingPreviews
|
|
||||||
if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
|
|
||||||
pos = pos.posmodv(Global.current_project.size)
|
|
||||||
|
|
||||||
var dst := rectangle_center(pos, custom_brush_size)
|
|
||||||
var src_rect := Rect2(Vector2.ZERO, custom_brush_size + Vector2.ONE)
|
|
||||||
# Rectangle with the same size as the brush, but at cursor's position
|
|
||||||
var pos_rect := Rect2(dst, custom_brush_size + Vector2.ONE)
|
|
||||||
|
|
||||||
# The selection rectangle
|
|
||||||
# If there's no rectangle, the whole canvas is considered a selection
|
|
||||||
var selection_rect := Rect2()
|
|
||||||
selection_rect.position = Vector2(x_min, y_min)
|
|
||||||
selection_rect.end = Vector2(x_max, y_max)
|
|
||||||
# Intersection of the position rectangle and selection
|
|
||||||
var pos_rect_clipped := pos_rect.clip(selection_rect)
|
|
||||||
# If the size is 0, that means that the brush wasn't positioned inside the selection
|
|
||||||
if pos_rect_clipped.size == Vector2.ZERO:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Re-position src_rect and dst based on the clipped position
|
|
||||||
var pos_difference := (pos_rect.position - pos_rect_clipped.position).abs()
|
|
||||||
# Obviously, if pos_rect and pos_rect_clipped are the same, pos_difference is Vector2.ZERO
|
|
||||||
src_rect.position = pos_difference
|
|
||||||
dst += pos_difference
|
|
||||||
src_rect.end -= pos_rect.end - pos_rect_clipped.end
|
|
||||||
# If the selection rectangle is smaller than the brush, ...
|
|
||||||
# ... make sure pixels aren't being drawn outside the selection by adjusting src_rect's size
|
|
||||||
src_rect.size.x = min(src_rect.size.x, selection_rect.size.x)
|
|
||||||
src_rect.size.y = min(src_rect.size.y, selection_rect.size.y)
|
|
||||||
|
|
||||||
# Handle mirroring
|
|
||||||
var mirror_x = x_max + x_min - pos.x - (pos.x - dst.x)
|
|
||||||
var mirror_y = y_max + y_min - pos.y - (pos.y - dst.y)
|
|
||||||
if int(pos_rect_clipped.size.x) % 2 != 0:
|
|
||||||
mirror_x -= 1
|
|
||||||
if int(pos_rect_clipped.size.y) % 2 != 0:
|
|
||||||
mirror_y -= 1
|
|
||||||
# Use custom blend function cause of godot's issue #31124
|
|
||||||
if color.a > 0: # If it's the pencil
|
|
||||||
sprite.blend_rect(custom_brush_image, src_rect, dst)
|
|
||||||
if horizontal_mirror:
|
|
||||||
sprite.blend_rect(custom_brush_image, src_rect, Vector2(mirror_x, dst.y))
|
|
||||||
if vertical_mirror:
|
|
||||||
sprite.blend_rect(custom_brush_image, src_rect, Vector2(dst.x, mirror_y))
|
|
||||||
if horizontal_mirror && vertical_mirror:
|
|
||||||
sprite.blend_rect(custom_brush_image, src_rect, Vector2(mirror_x, mirror_y))
|
|
||||||
|
|
||||||
else: # if it's transparent - if it's the eraser
|
|
||||||
var custom_brush := Image.new()
|
|
||||||
if brush_type == Global.Brush_Types.CUSTOM:
|
|
||||||
custom_brush.copy_from(Global.current_project.brushes[brush_index])
|
|
||||||
else:
|
|
||||||
custom_brush.copy_from(Global.file_brushes[brush_index])
|
|
||||||
custom_brush_size = custom_brush.get_size()
|
|
||||||
custom_brush.resize(custom_brush_size.x * brush_size, custom_brush_size.y * brush_size, Image.INTERPOLATE_NEAREST)
|
|
||||||
var custom_brush_blended = Global.blend_image_with_color(custom_brush, color, 1)
|
|
||||||
|
|
||||||
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, dst)
|
|
||||||
if horizontal_mirror:
|
|
||||||
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(mirror_x, dst.y))
|
|
||||||
if vertical_mirror:
|
|
||||||
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(dst.x, mirror_y))
|
|
||||||
if horizontal_mirror && vertical_mirror:
|
|
||||||
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(mirror_x, mirror_y))
|
|
||||||
|
|
||||||
sprite.lock()
|
|
||||||
Global.canvas.sprite_changed_this_frame = true
|
|
||||||
|
|
||||||
Global.canvas.previous_mouse_pos_for_lines = pos.floor() + Vector2(0.5, 0.5)
|
|
||||||
Global.canvas.previous_mouse_pos_for_lines.x = clamp(Global.canvas.previous_mouse_pos_for_lines.x, Global.canvas.location.x, Global.canvas.location.x + Global.current_project.size.x)
|
|
||||||
Global.canvas.previous_mouse_pos_for_lines.y = clamp(Global.canvas.previous_mouse_pos_for_lines.y, Global.canvas.location.y, Global.canvas.location.y + Global.current_project.size.y)
|
|
||||||
if Global.canvas.is_making_line:
|
|
||||||
Global.canvas.line_pos[0] = Global.canvas.previous_mouse_pos_for_lines
|
|
||||||
|
|
||||||
|
|
||||||
# Bresenham's Algorithm
|
|
||||||
# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
|
|
||||||
func fill_gaps(sprite : Image, end_pos : Vector2, start_pos : Vector2, color : Color, current_mouse_button : int, pen_pressure : float, current_action := -1) -> void:
|
|
||||||
var previous_mouse_pos_floored = start_pos.floor()
|
|
||||||
var mouse_pos_floored = end_pos.floor()
|
|
||||||
var dx := int(abs(mouse_pos_floored.x - previous_mouse_pos_floored.x))
|
|
||||||
var dy := int(-abs(mouse_pos_floored.y - previous_mouse_pos_floored.y))
|
|
||||||
var err := dx + dy
|
|
||||||
var e2 := err << 1 # err * 2
|
|
||||||
var sx = 1 if previous_mouse_pos_floored.x < mouse_pos_floored.x else -1
|
|
||||||
var sy = 1 if previous_mouse_pos_floored.y < mouse_pos_floored.y else -1
|
|
||||||
var x = previous_mouse_pos_floored.x
|
|
||||||
var y = previous_mouse_pos_floored.y
|
|
||||||
while !(x == mouse_pos_floored.x && y == mouse_pos_floored.y):
|
|
||||||
draw_brush(sprite, Vector2(x, y), color, current_mouse_button, pen_pressure, current_action)
|
|
||||||
e2 = err << 1
|
|
||||||
if e2 >= dy:
|
|
||||||
err += dy
|
|
||||||
x += sx
|
|
||||||
if e2 <= dx:
|
|
||||||
err += dx
|
|
||||||
y += sy
|
|
||||||
|
|
||||||
|
|
||||||
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
|
||||||
func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, pen_pressure : float, fill := false) -> void:
|
|
||||||
var radius := r # Used later for filling
|
|
||||||
var x := -r
|
|
||||||
var y := 0
|
|
||||||
var err := 2 - r * 2 # II. Quadrant
|
|
||||||
while x < 0:
|
|
||||||
var quadrant_1 := Vector2(xm - x, ym + y)
|
|
||||||
var quadrant_2 := Vector2(xm - y, ym - x)
|
|
||||||
var quadrant_3 := Vector2(xm + x, ym - y)
|
|
||||||
var quadrant_4 := Vector2(xm + y, ym + x)
|
|
||||||
draw_pixel_blended(sprite, quadrant_1, color, pen_pressure)
|
|
||||||
draw_pixel_blended(sprite, quadrant_2, color, pen_pressure)
|
|
||||||
draw_pixel_blended(sprite, quadrant_3, color, pen_pressure)
|
|
||||||
draw_pixel_blended(sprite, quadrant_4, color, pen_pressure)
|
|
||||||
|
|
||||||
r = err
|
|
||||||
if r <= y:
|
|
||||||
y += 1
|
|
||||||
err += y * 2 + 1
|
|
||||||
if r > x || err > y:
|
|
||||||
x += 1
|
|
||||||
err += x * 2 + 1
|
|
||||||
|
|
||||||
if fill:
|
|
||||||
for j in range (-radius, radius + 1):
|
|
||||||
for i in range (-radius, radius + 1):
|
|
||||||
if i * i + j * j <= radius * radius:
|
|
||||||
var draw_pos := Vector2(i + xm, j + ym)
|
|
||||||
draw_pixel_blended(sprite, draw_pos, color, Global.canvas.pen_pressure)
|
|
||||||
|
|
||||||
|
|
||||||
# Thanks to https://en.wikipedia.org/wiki/Flood_fill
|
|
||||||
func flood_fill(sprite : Image, pos : Vector2, target_color : Color, replace_color : Color) -> void:
|
|
||||||
var x_min = Global.current_project.x_min
|
|
||||||
var x_max = Global.current_project.x_max
|
|
||||||
var y_min = Global.current_project.y_min
|
|
||||||
var y_max = Global.current_project.y_max
|
|
||||||
pos = pos.floor()
|
|
||||||
var pixel = sprite.get_pixelv(pos)
|
|
||||||
if target_color == replace_color:
|
|
||||||
return
|
|
||||||
elif pixel != target_color:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
|
|
||||||
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
|
|
||||||
return
|
|
||||||
|
|
||||||
var q = [pos]
|
|
||||||
for n in q:
|
|
||||||
# If the difference in colors is very small, break the loop (thanks @azagaya on GitHub!)
|
|
||||||
if target_color == replace_color:
|
|
||||||
break
|
|
||||||
var west : Vector2 = n
|
|
||||||
var east : Vector2 = n
|
|
||||||
while west.x >= x_min && sprite.get_pixelv(west) == target_color:
|
|
||||||
west += Vector2.LEFT
|
|
||||||
while east.x < x_max && sprite.get_pixelv(east) == target_color:
|
|
||||||
east += Vector2.RIGHT
|
|
||||||
for px in range(west.x + 1, east.x):
|
|
||||||
var p := Vector2(px, n.y)
|
|
||||||
# Draw
|
|
||||||
sprite.set_pixelv(p, replace_color)
|
|
||||||
replace_color = sprite.get_pixelv(p)
|
|
||||||
var north := p + Vector2.UP
|
|
||||||
var south := p + Vector2.DOWN
|
|
||||||
if north.y >= y_min && sprite.get_pixelv(north) == target_color:
|
|
||||||
q.append(north)
|
|
||||||
if south.y < y_max && sprite.get_pixelv(south) == target_color:
|
|
||||||
q.append(south)
|
|
||||||
|
|
||||||
Global.canvas.sprite_changed_this_frame = true
|
|
||||||
|
|
||||||
|
|
||||||
func pattern_fill(sprite : Image, pos : Vector2, pattern : Image, target_color : Color, var offset : Vector2) -> void:
|
|
||||||
var x_min = Global.current_project.x_min
|
|
||||||
var x_max = Global.current_project.x_max
|
|
||||||
var y_min = Global.current_project.y_min
|
|
||||||
var y_max = Global.current_project.y_max
|
|
||||||
pos = pos.floor()
|
|
||||||
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
|
|
||||||
return
|
|
||||||
|
|
||||||
pattern.lock()
|
|
||||||
var pattern_size := pattern.get_size()
|
|
||||||
var q = [pos]
|
|
||||||
|
|
||||||
for n in q:
|
|
||||||
var west : Vector2 = n
|
|
||||||
var east : Vector2 = n
|
|
||||||
while west.x >= x_min && sprite.get_pixelv(west) == target_color:
|
|
||||||
west += Vector2.LEFT
|
|
||||||
while east.x < x_max && sprite.get_pixelv(east) == target_color:
|
|
||||||
east += Vector2.RIGHT
|
|
||||||
|
|
||||||
for px in range(west.x + 1, east.x):
|
|
||||||
var p := Vector2(px, n.y)
|
|
||||||
var xx : int = int(px + offset.x) % int(pattern_size.x)
|
|
||||||
var yy : int = int(n.y + offset.y) % int(pattern_size.y)
|
|
||||||
var pattern_color : Color = pattern.get_pixel(xx, yy)
|
|
||||||
if pattern_color == target_color:
|
|
||||||
continue
|
|
||||||
sprite.set_pixelv(p, pattern_color)
|
|
||||||
|
|
||||||
var north := p + Vector2.UP
|
|
||||||
var south := p + Vector2.DOWN
|
|
||||||
if north.y >= y_min && sprite.get_pixelv(north) == target_color:
|
|
||||||
q.append(north)
|
|
||||||
if south.y < y_max && sprite.get_pixelv(south) == target_color:
|
|
||||||
q.append(south)
|
|
||||||
|
|
||||||
pattern.unlock()
|
|
||||||
Global.canvas.sprite_changed_this_frame = true
|
|
||||||
|
|
||||||
|
|
||||||
func blend_colors(color_1 : Color, color_2 : Color) -> Color:
|
|
||||||
var color := Color()
|
|
||||||
color.a = color_1.a + color_2.a * (1 - color_1.a) # Blend alpha
|
|
||||||
if color.a != 0:
|
|
||||||
# Blend colors
|
|
||||||
color.r = (color_1.r * color_1.a + color_2.r * color_2.a * (1-color_1.a)) / color.a
|
|
||||||
color.g = (color_1.g * color_1.a + color_2.g * color_2.a * (1-color_1.a)) / color.a
|
|
||||||
color.b = (color_1.b * color_1.a + color_2.b * color_2.a * (1-color_1.a)) / color.a
|
|
||||||
return color
|
|
||||||
|
|
||||||
|
|
||||||
func scale3X(sprite : Image, tol : float = 50) -> Image:
|
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.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8)
|
||||||
|
@ -829,13 +499,3 @@ func adjust_hsv(img: Image, id : int, delta : float) -> void:
|
||||||
img.set_pixel(i,j,c)
|
img.set_pixel(i,j,c)
|
||||||
|
|
||||||
img.unlock()
|
img.unlock()
|
||||||
|
|
||||||
|
|
||||||
# Checks if a point is inside a rectangle
|
|
||||||
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
|
||||||
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y
|
|
||||||
|
|
||||||
|
|
||||||
# Returns the position in the middle of a rectangle
|
|
||||||
func rectangle_center(rect_position : Vector2, rect_size : Vector2) -> Vector2:
|
|
||||||
return (rect_position - rect_size / 2).floor()
|
|
||||||
|
|
|
@ -3,15 +3,8 @@ extends Node
|
||||||
|
|
||||||
enum Grid_Types {CARTESIAN, ISOMETRIC, ALL}
|
enum Grid_Types {CARTESIAN, ISOMETRIC, ALL}
|
||||||
enum Pressure_Sensitivity {NONE, ALPHA, SIZE, ALPHA_AND_SIZE}
|
enum Pressure_Sensitivity {NONE, ALPHA, SIZE, ALPHA_AND_SIZE}
|
||||||
enum Brush_Types {PIXEL, CIRCLE, FILLED_CIRCLE, FILE, RANDOM_FILE, CUSTOM}
|
|
||||||
enum Direction {UP, DOWN, LEFT, RIGHT}
|
enum Direction {UP, DOWN, LEFT, RIGHT}
|
||||||
enum Mouse_Button {LEFT, RIGHT}
|
|
||||||
enum Tools {PENCIL, ERASER, BUCKET, LIGHTENDARKEN, RECTSELECT, COLORPICKER, ZOOM}
|
|
||||||
enum Theme_Types {DARK, BLUE, CARAMEL, LIGHT}
|
enum Theme_Types {DARK, BLUE, CARAMEL, LIGHT}
|
||||||
enum Fill_Area {SAME_COLOR_AREA, SAME_COLOR_PIXELS}
|
|
||||||
enum Fill_With {COLOR, PATTERN}
|
|
||||||
enum Lighten_Darken_Mode {LIGHTEN, DARKEN}
|
|
||||||
enum Zoom_Mode {ZOOM_IN, ZOOM_OUT}
|
|
||||||
|
|
||||||
# Stuff for arrowkey-based canvas movements nyaa ^.^
|
# Stuff for arrowkey-based canvas movements nyaa ^.^
|
||||||
const low_speed_move_rate := 150.0
|
const low_speed_move_rate := 150.0
|
||||||
|
@ -44,8 +37,8 @@ var pressure_sensitivity_mode = Pressure_Sensitivity.NONE
|
||||||
var open_last_project := false
|
var open_last_project := false
|
||||||
var smooth_zoom := true
|
var smooth_zoom := true
|
||||||
var cursor_image = preload("res://assets/graphics/cursor_icons/cursor.png")
|
var cursor_image = preload("res://assets/graphics/cursor_icons/cursor.png")
|
||||||
var left_cursor_tool_texture : ImageTexture
|
var left_cursor_tool_texture := ImageTexture.new()
|
||||||
var right_cursor_tool_texture : ImageTexture
|
var right_cursor_tool_texture := ImageTexture.new()
|
||||||
|
|
||||||
var image_clipboard : Image
|
var image_clipboard : Image
|
||||||
var play_only_tags := true
|
var play_only_tags := true
|
||||||
|
@ -68,27 +61,11 @@ var autosave_interval := 5.0
|
||||||
var enable_autosave := true
|
var enable_autosave := true
|
||||||
|
|
||||||
# Tools & options
|
# Tools & options
|
||||||
var current_tools := [Tools.PENCIL, Tools.ERASER]
|
|
||||||
var show_left_tool_icon := true
|
var show_left_tool_icon := true
|
||||||
var show_right_tool_icon := true
|
var show_right_tool_icon := true
|
||||||
var left_square_indicator_visible := true
|
var left_square_indicator_visible := true
|
||||||
var right_square_indicator_visible := false
|
var right_square_indicator_visible := false
|
||||||
|
|
||||||
var fill_areas := [Fill_Area.SAME_COLOR_AREA, Fill_Area.SAME_COLOR_AREA]
|
|
||||||
var fill_with := [Fill_With.COLOR, Fill_With.COLOR]
|
|
||||||
var fill_pattern_offsets := [Vector2.ZERO, Vector2.ZERO]
|
|
||||||
|
|
||||||
var ld_modes := [Lighten_Darken_Mode.LIGHTEN, Lighten_Darken_Mode.LIGHTEN]
|
|
||||||
var ld_amounts := [0.1, 0.1]
|
|
||||||
|
|
||||||
var color_picker_for := [Mouse_Button.LEFT, Mouse_Button.RIGHT]
|
|
||||||
|
|
||||||
var zoom_modes := [Zoom_Mode.ZOOM_IN, Zoom_Mode.ZOOM_OUT]
|
|
||||||
|
|
||||||
var horizontal_mirror := [false, false]
|
|
||||||
var vertical_mirror := [false, false]
|
|
||||||
var pixel_perfect := [false, false]
|
|
||||||
|
|
||||||
# View menu options
|
# View menu options
|
||||||
var tile_mode := false
|
var tile_mode := false
|
||||||
var draw_grid := false
|
var draw_grid := false
|
||||||
|
@ -102,25 +79,6 @@ var onion_skinning_past_rate := 1.0
|
||||||
var onion_skinning_future_rate := 1.0
|
var onion_skinning_future_rate := 1.0
|
||||||
var onion_skinning_blue_red := false
|
var onion_skinning_blue_red := false
|
||||||
|
|
||||||
# Brushes
|
|
||||||
var file_brushes := []
|
|
||||||
var brush_sizes := [1, 1]
|
|
||||||
var current_brush_types := [Brush_Types.PIXEL, Brush_Types.PIXEL]
|
|
||||||
var brush_images := [Image.new(), Image.new()]
|
|
||||||
var brush_textures := [ImageTexture.new(), ImageTexture.new()]
|
|
||||||
|
|
||||||
var brush_type_window_position : int = Mouse_Button.LEFT
|
|
||||||
var left_circle_points := []
|
|
||||||
var right_circle_points := []
|
|
||||||
|
|
||||||
var brushes_from_files := 0
|
|
||||||
var custom_brush_indexes := [-1, -1]
|
|
||||||
|
|
||||||
# Patterns
|
|
||||||
var patterns := []
|
|
||||||
var pattern_window_position : int = Mouse_Button.LEFT
|
|
||||||
var pattern_images := [Image.new(), Image.new()]
|
|
||||||
|
|
||||||
# Palettes
|
# Palettes
|
||||||
var palettes := {}
|
var palettes := {}
|
||||||
|
|
||||||
|
@ -158,41 +116,11 @@ var export_dialog : AcceptDialog
|
||||||
var preferences_dialog : AcceptDialog
|
var preferences_dialog : AcceptDialog
|
||||||
var unsaved_changes_dialog : ConfirmationDialog
|
var unsaved_changes_dialog : ConfirmationDialog
|
||||||
|
|
||||||
var color_pickers := []
|
|
||||||
|
|
||||||
var color_switch_button : BaseButton
|
var color_switch_button : BaseButton
|
||||||
|
|
||||||
var tool_options_containers := []
|
|
||||||
|
|
||||||
var brush_type_containers := []
|
|
||||||
var brush_type_buttons := []
|
|
||||||
var brushes_popup : Popup
|
var brushes_popup : Popup
|
||||||
var file_brush_container : GridContainer
|
|
||||||
var project_brush_container : GridContainer
|
|
||||||
var patterns_popup : Popup
|
var patterns_popup : Popup
|
||||||
|
|
||||||
var brush_size_edits := []
|
|
||||||
var brush_size_sliders := []
|
|
||||||
|
|
||||||
var pixel_perfect_containers := []
|
|
||||||
|
|
||||||
var color_interpolation_containers := []
|
|
||||||
var interpolate_spinboxes := []
|
|
||||||
var interpolate_sliders := []
|
|
||||||
|
|
||||||
var fill_area_containers := []
|
|
||||||
var fill_pattern_containers := []
|
|
||||||
|
|
||||||
var ld_containers := []
|
|
||||||
var ld_amount_sliders := []
|
|
||||||
var ld_amount_spinboxes := []
|
|
||||||
|
|
||||||
var colorpicker_containers := []
|
|
||||||
|
|
||||||
var zoom_containers := []
|
|
||||||
|
|
||||||
var mirror_containers := []
|
|
||||||
|
|
||||||
var animation_timeline : Panel
|
var animation_timeline : Panel
|
||||||
|
|
||||||
var animation_timer : Timer
|
var animation_timer : Timer
|
||||||
|
@ -251,21 +179,6 @@ func _ready() -> void:
|
||||||
right_cursor = find_node_by_name(root, "RightCursor")
|
right_cursor = find_node_by_name(root, "RightCursor")
|
||||||
canvas = find_node_by_name(root, "Canvas")
|
canvas = find_node_by_name(root, "Canvas")
|
||||||
|
|
||||||
var pencil_cursor_image = preload("res://assets/graphics/cursor_icons/pencil_cursor.png")
|
|
||||||
var eraser_cursor_image = preload("res://assets/graphics/cursor_icons/eraser_cursor.png")
|
|
||||||
|
|
||||||
left_cursor_tool_texture = ImageTexture.new()
|
|
||||||
if pencil_cursor_image is Image:
|
|
||||||
left_cursor_tool_texture.create_from_image(pencil_cursor_image)
|
|
||||||
elif pencil_cursor_image is ImageTexture:
|
|
||||||
left_cursor_tool_texture.create_from_image(pencil_cursor_image.get_data())
|
|
||||||
|
|
||||||
right_cursor_tool_texture = ImageTexture.new()
|
|
||||||
if eraser_cursor_image is Image:
|
|
||||||
right_cursor_tool_texture.create_from_image(eraser_cursor_image)
|
|
||||||
elif eraser_cursor_image is ImageTexture:
|
|
||||||
right_cursor_tool_texture.create_from_image(eraser_cursor_image.get_data())
|
|
||||||
|
|
||||||
tabs = find_node_by_name(root, "Tabs")
|
tabs = find_node_by_name(root, "Tabs")
|
||||||
main_viewport = find_node_by_name(root, "ViewportContainer")
|
main_viewport = find_node_by_name(root, "ViewportContainer")
|
||||||
second_viewport = find_node_by_name(root, "ViewportContainer2")
|
second_viewport = find_node_by_name(root, "ViewportContainer2")
|
||||||
|
@ -294,58 +207,11 @@ func _ready() -> void:
|
||||||
preferences_dialog = find_node_by_name(root, "PreferencesDialog")
|
preferences_dialog = find_node_by_name(root, "PreferencesDialog")
|
||||||
unsaved_changes_dialog = find_node_by_name(root, "UnsavedCanvasDialog")
|
unsaved_changes_dialog = find_node_by_name(root, "UnsavedCanvasDialog")
|
||||||
|
|
||||||
tool_options_containers.append(find_node_by_name(root, "LeftToolOptions"))
|
|
||||||
tool_options_containers.append(find_node_by_name(root, "RightToolOptions"))
|
|
||||||
|
|
||||||
color_pickers.append(find_node_by_name(root, "LeftColorPickerButton"))
|
|
||||||
color_pickers.append(find_node_by_name(root, "RightColorPickerButton"))
|
|
||||||
color_switch_button = find_node_by_name(root, "ColorSwitch")
|
color_switch_button = find_node_by_name(root, "ColorSwitch")
|
||||||
|
|
||||||
brush_type_containers.append(find_node_by_name(tool_options_containers[0], "LeftBrushType"))
|
|
||||||
brush_type_containers.append(find_node_by_name(tool_options_containers[1], "RightBrushType"))
|
|
||||||
brush_type_buttons.append(find_node_by_name(brush_type_containers[0], "LeftBrushTypeButton"))
|
|
||||||
brush_type_buttons.append(find_node_by_name(brush_type_containers[1], "RightBrushTypeButton"))
|
|
||||||
brushes_popup = find_node_by_name(root, "BrushesPopup")
|
brushes_popup = find_node_by_name(root, "BrushesPopup")
|
||||||
file_brush_container = find_node_by_name(brushes_popup, "FileBrushContainer")
|
|
||||||
project_brush_container = find_node_by_name(brushes_popup, "ProjectBrushContainer")
|
|
||||||
patterns_popup = find_node_by_name(root, "PatternsPopup")
|
patterns_popup = find_node_by_name(root, "PatternsPopup")
|
||||||
|
|
||||||
brush_size_edits.append(find_node_by_name(root, "LeftBrushSizeEdit"))
|
|
||||||
brush_size_sliders.append(find_node_by_name(root, "LeftBrushSizeSlider"))
|
|
||||||
brush_size_edits.append(find_node_by_name(root, "RightBrushSizeEdit"))
|
|
||||||
brush_size_sliders.append(find_node_by_name(root, "RightBrushSizeSlider"))
|
|
||||||
|
|
||||||
pixel_perfect_containers.append(find_node_by_name(root, "LeftBrushPixelPerfectMode"))
|
|
||||||
pixel_perfect_containers.append(find_node_by_name(root, "RightBrushPixelPerfectMode"))
|
|
||||||
|
|
||||||
color_interpolation_containers.append(find_node_by_name(root, "LeftColorInterpolation"))
|
|
||||||
color_interpolation_containers.append(find_node_by_name(root, "RightColorInterpolation"))
|
|
||||||
interpolate_spinboxes.append(find_node_by_name(root, "LeftInterpolateFactor"))
|
|
||||||
interpolate_sliders.append(find_node_by_name(root, "LeftInterpolateSlider"))
|
|
||||||
interpolate_spinboxes.append(find_node_by_name(root, "RightInterpolateFactor"))
|
|
||||||
interpolate_sliders.append(find_node_by_name(root, "RightInterpolateSlider"))
|
|
||||||
|
|
||||||
fill_area_containers.append(find_node_by_name(root, "LeftFillArea"))
|
|
||||||
fill_pattern_containers.append(find_node_by_name(root, "LeftFillPattern"))
|
|
||||||
fill_area_containers.append(find_node_by_name(root, "RightFillArea"))
|
|
||||||
fill_pattern_containers.append(find_node_by_name(root, "RightFillPattern"))
|
|
||||||
|
|
||||||
ld_containers.append(find_node_by_name(root, "LeftLDOptions"))
|
|
||||||
ld_amount_sliders.append(find_node_by_name(root, "LeftLDAmountSlider"))
|
|
||||||
ld_amount_spinboxes.append(find_node_by_name(root, "LeftLDAmountSpinbox"))
|
|
||||||
ld_containers.append(find_node_by_name(root, "RightLDOptions"))
|
|
||||||
ld_amount_sliders.append(find_node_by_name(root, "RightLDAmountSlider"))
|
|
||||||
ld_amount_spinboxes.append(find_node_by_name(root, "RightLDAmountSpinbox"))
|
|
||||||
|
|
||||||
colorpicker_containers.append(find_node_by_name(root, "LeftColorPickerOptions"))
|
|
||||||
colorpicker_containers.append(find_node_by_name(root, "RightColorPickerOptions"))
|
|
||||||
|
|
||||||
zoom_containers.append(find_node_by_name(root, "LeftZoomOptions"))
|
|
||||||
zoom_containers.append(find_node_by_name(root, "RightZoomOptions"))
|
|
||||||
|
|
||||||
mirror_containers.append(find_node_by_name(root, "LeftMirrorButtons"))
|
|
||||||
mirror_containers.append(find_node_by_name(root, "RightMirrorButtons"))
|
|
||||||
|
|
||||||
animation_timeline = find_node_by_name(root, "AnimationTimeline")
|
animation_timeline = find_node_by_name(root, "AnimationTimeline")
|
||||||
|
|
||||||
layers_container = find_node_by_name(animation_timeline, "LayersContainer")
|
layers_container = find_node_by_name(animation_timeline, "LayersContainer")
|
||||||
|
@ -605,143 +471,6 @@ Hold %s to make a line""") % [InputMap.get_action_list("left_eraser_tool")[0].as
|
||||||
(%s)""") % InputMap.get_action_list("go_to_last_frame")[0].as_text()
|
(%s)""") % InputMap.get_action_list("go_to_last_frame")[0].as_text()
|
||||||
|
|
||||||
|
|
||||||
func create_brush_button(brush_img : Image, brush_type := Brush_Types.CUSTOM, hint_tooltip := "") -> void:
|
|
||||||
var brush_container
|
|
||||||
var brush_button = load("res://src/UI/BrushButton.tscn").instance()
|
|
||||||
brush_button.brush_type = brush_type
|
|
||||||
if brush_type == Brush_Types.FILE || brush_type == Brush_Types.RANDOM_FILE:
|
|
||||||
brush_button.custom_brush_index = file_brushes.size() - 1
|
|
||||||
brush_container = file_brush_container
|
|
||||||
else:
|
|
||||||
brush_button.custom_brush_index = current_project.brushes.size() - 1
|
|
||||||
brush_container = project_brush_container
|
|
||||||
var brush_tex := ImageTexture.new()
|
|
||||||
brush_tex.create_from_image(brush_img, 0)
|
|
||||||
brush_button.get_child(0).texture = brush_tex
|
|
||||||
brush_button.hint_tooltip = hint_tooltip
|
|
||||||
brush_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
||||||
if brush_type == Brush_Types.RANDOM_FILE:
|
|
||||||
brush_button.random_brushes.append(brush_img)
|
|
||||||
brush_container.add_child(brush_button)
|
|
||||||
|
|
||||||
|
|
||||||
func create_pattern_button(image : Image, hint_tooltip := "") -> void:
|
|
||||||
var pattern_button : BaseButton = load("res://src/UI/PatternButton.tscn").instance()
|
|
||||||
pattern_button.image = image
|
|
||||||
var pattern_tex := ImageTexture.new()
|
|
||||||
pattern_tex.create_from_image(image, 0)
|
|
||||||
pattern_button.get_child(0).texture = pattern_tex
|
|
||||||
pattern_button.hint_tooltip = hint_tooltip
|
|
||||||
pattern_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
||||||
patterns_popup.get_node("ScrollContainer/PatternContainer").add_child(pattern_button)
|
|
||||||
|
|
||||||
|
|
||||||
func remove_brush_buttons() -> void:
|
|
||||||
current_brush_types[0] = Brush_Types.PIXEL
|
|
||||||
current_brush_types[1] = Brush_Types.PIXEL
|
|
||||||
for child in project_brush_container.get_children():
|
|
||||||
child.queue_free()
|
|
||||||
|
|
||||||
|
|
||||||
func undo_custom_brush(_brush_button : BaseButton = null) -> void:
|
|
||||||
general_undo()
|
|
||||||
var action_name : String = current_project.undo_redo.get_current_action_name()
|
|
||||||
if action_name == "Delete Custom Brush":
|
|
||||||
project_brush_container.add_child(_brush_button)
|
|
||||||
project_brush_container.move_child(_brush_button, _brush_button.custom_brush_index)
|
|
||||||
_brush_button.get_node("DeleteButton").visible = false
|
|
||||||
|
|
||||||
|
|
||||||
func redo_custom_brush(_brush_button : BaseButton = null) -> void:
|
|
||||||
general_redo()
|
|
||||||
var action_name : String = current_project.undo_redo.get_current_action_name()
|
|
||||||
if action_name == "Delete Custom Brush":
|
|
||||||
project_brush_container.remove_child(_brush_button)
|
|
||||||
|
|
||||||
|
|
||||||
func update_custom_brush(mouse_button : int) -> void:
|
|
||||||
var brush_type : int = current_brush_types[mouse_button]
|
|
||||||
if brush_type == Brush_Types.PIXEL:
|
|
||||||
var pixel = preload("res://assets/graphics/pixel_image.png")
|
|
||||||
if pixel is Image:
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0)
|
|
||||||
elif pixel is ImageTexture:
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0)
|
|
||||||
elif brush_type == Brush_Types.CIRCLE:
|
|
||||||
var pixel = preload("res://assets/graphics/circle_9x9.png")
|
|
||||||
if pixel is Image:
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0)
|
|
||||||
elif pixel is ImageTexture:
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0)
|
|
||||||
|
|
||||||
left_circle_points = plot_circle(brush_sizes[0])
|
|
||||||
right_circle_points = plot_circle(brush_sizes[1])
|
|
||||||
elif brush_type == Brush_Types.FILLED_CIRCLE:
|
|
||||||
var pixel = preload("res://assets/graphics/circle_filled_9x9.png")
|
|
||||||
if pixel is Image:
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0)
|
|
||||||
elif pixel is ImageTexture:
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0)
|
|
||||||
|
|
||||||
left_circle_points = plot_circle(brush_sizes[0])
|
|
||||||
right_circle_points = plot_circle(brush_sizes[1])
|
|
||||||
else:
|
|
||||||
var custom_brush := Image.new()
|
|
||||||
if brush_type == Brush_Types.FILE or brush_type == Brush_Types.RANDOM_FILE:
|
|
||||||
custom_brush.copy_from(file_brushes[custom_brush_indexes[mouse_button]])
|
|
||||||
else:
|
|
||||||
custom_brush.copy_from(current_project.brushes[custom_brush_indexes[mouse_button]])
|
|
||||||
var custom_brush_size = custom_brush.get_size()
|
|
||||||
custom_brush.resize(custom_brush_size.x * brush_sizes[mouse_button], custom_brush_size.y * brush_sizes[mouse_button], Image.INTERPOLATE_NEAREST)
|
|
||||||
brush_images[mouse_button] = blend_image_with_color(custom_brush, color_pickers[mouse_button].color, interpolate_spinboxes[mouse_button].value / 100)
|
|
||||||
brush_textures[mouse_button].create_from_image(brush_images[mouse_button], 0)
|
|
||||||
|
|
||||||
brush_type_buttons[mouse_button].get_child(0).texture = brush_textures[mouse_button]
|
|
||||||
|
|
||||||
|
|
||||||
func blend_image_with_color(image : Image, color : Color, interpolate_factor : float) -> Image:
|
|
||||||
var blended_image := Image.new()
|
|
||||||
blended_image.copy_from(image)
|
|
||||||
var size := image.get_size()
|
|
||||||
blended_image.lock()
|
|
||||||
for xx in size.x:
|
|
||||||
for yy in size.y:
|
|
||||||
if color.a > 0: # If it's the pencil
|
|
||||||
var current_color := blended_image.get_pixel(xx, yy)
|
|
||||||
if current_color.a > 0:
|
|
||||||
var new_color := current_color.linear_interpolate(color, interpolate_factor)
|
|
||||||
new_color.a = current_color.a
|
|
||||||
blended_image.set_pixel(xx, yy, new_color)
|
|
||||||
else: # If color is transparent - if it's the eraser
|
|
||||||
blended_image.set_pixel(xx, yy, Color(0, 0, 0, 0))
|
|
||||||
return blended_image
|
|
||||||
|
|
||||||
|
|
||||||
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
|
||||||
# This is not used for drawing, rather for finding the points required
|
|
||||||
# for the mouse cursor/position indicator
|
|
||||||
func plot_circle(r : int) -> Array:
|
|
||||||
var circle_points := []
|
|
||||||
var xm := 0
|
|
||||||
var ym := 0
|
|
||||||
var x := -r
|
|
||||||
var y := 0
|
|
||||||
var err := 2 - r * 2
|
|
||||||
while x < 0:
|
|
||||||
circle_points.append(Vector2(xm - x, ym + y))
|
|
||||||
circle_points.append(Vector2(xm - y, ym - x))
|
|
||||||
circle_points.append(Vector2(xm + x, ym - y))
|
|
||||||
circle_points.append(Vector2(xm + y, ym + x))
|
|
||||||
r = err
|
|
||||||
if r <= y:
|
|
||||||
y += 1
|
|
||||||
err += y * 2 + 1
|
|
||||||
if r > x || err > y:
|
|
||||||
x += 1
|
|
||||||
err += x * 2 + 1
|
|
||||||
return circle_points
|
|
||||||
|
|
||||||
|
|
||||||
func _exit_tree() -> void:
|
func _exit_tree() -> void:
|
||||||
config_cache.set_value("window", "screen", OS.current_screen)
|
config_cache.set_value("window", "screen", OS.current_screen)
|
||||||
config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen)
|
config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen)
|
||||||
|
|
|
@ -105,18 +105,7 @@ func add_randomised_brush(fpaths : Array, tooltip_name : String) -> void:
|
||||||
|
|
||||||
if len(loaded_images) > 0: # actually have images
|
if len(loaded_images) > 0: # actually have images
|
||||||
# to use.
|
# to use.
|
||||||
# take initial image...
|
Brushes.add_file_brush(loaded_images, tooltip_name)
|
||||||
var first_image : Image = loaded_images.pop_front()
|
|
||||||
|
|
||||||
# The index which this random brush will be at
|
|
||||||
var next_random_brush_index : int = Global.file_brush_container.get_child_count()
|
|
||||||
|
|
||||||
Global.file_brushes.append(first_image)
|
|
||||||
Global.create_brush_button(first_image, Global.Brush_Types.RANDOM_FILE, tooltip_name)
|
|
||||||
# # Process the rest
|
|
||||||
for remaining_image in loaded_images:
|
|
||||||
var brush_button = Global.file_brush_container.get_child(next_random_brush_index)
|
|
||||||
brush_button.random_brushes.append(remaining_image)
|
|
||||||
|
|
||||||
# Add a plain brush from the given path to the list of brushes.
|
# Add a plain brush from the given path to the list of brushes.
|
||||||
# Taken, again, from find_brushes
|
# Taken, again, from find_brushes
|
||||||
|
@ -127,8 +116,7 @@ func add_plain_brush(path: String, tooltip_name: String) -> void:
|
||||||
return
|
return
|
||||||
# do the standard conversion thing...
|
# do the standard conversion thing...
|
||||||
image.convert(Image.FORMAT_RGBA8)
|
image.convert(Image.FORMAT_RGBA8)
|
||||||
Global.file_brushes.append(image)
|
Brushes.add_file_brush([image], tooltip_name)
|
||||||
Global.create_brush_button(image, Global.Brush_Types.FILE, tooltip_name)
|
|
||||||
|
|
||||||
|
|
||||||
# Import brushes, in priority order, from the paths in question in priority order
|
# Import brushes, in priority order, from the paths in question in priority order
|
||||||
|
@ -214,8 +202,6 @@ func import_brushes(priority_ordered_search_path: Array) -> void:
|
||||||
# Mark this as a processed relpath
|
# Mark this as a processed relpath
|
||||||
processed_subdir_paths[nonrandomised_subdir][relative_path] = true
|
processed_subdir_paths[nonrandomised_subdir][relative_path] = true
|
||||||
|
|
||||||
Global.brushes_from_files = Global.file_brushes.size()
|
|
||||||
|
|
||||||
|
|
||||||
func import_patterns(priority_ordered_search_path: Array) -> void:
|
func import_patterns(priority_ordered_search_path: Array) -> void:
|
||||||
for path in priority_ordered_search_path:
|
for path in priority_ordered_search_path:
|
||||||
|
@ -235,25 +221,7 @@ func import_patterns(priority_ordered_search_path: Array) -> void:
|
||||||
var err := image.load(path.plus_file(pattern))
|
var err := image.load(path.plus_file(pattern))
|
||||||
if err == OK:
|
if err == OK:
|
||||||
image.convert(Image.FORMAT_RGBA8)
|
image.convert(Image.FORMAT_RGBA8)
|
||||||
Global.patterns.append(image)
|
Global.patterns_popup.add(image)
|
||||||
Global.create_pattern_button(image, pattern)
|
|
||||||
|
|
||||||
if Global.patterns.size() > 0:
|
|
||||||
var image_size = Global.patterns[0].get_size()
|
|
||||||
|
|
||||||
Global.pattern_images[0] = Global.patterns[0]
|
|
||||||
var pattern_left_tex := ImageTexture.new()
|
|
||||||
pattern_left_tex.create_from_image(Global.pattern_images[0], 0)
|
|
||||||
Global.fill_pattern_containers[0].get_child(0).get_child(0).texture = pattern_left_tex
|
|
||||||
Global.fill_pattern_containers[0].get_child(2).get_child(1).max_value = image_size.x - 1
|
|
||||||
Global.fill_pattern_containers[0].get_child(3).get_child(1).max_value = image_size.y - 1
|
|
||||||
|
|
||||||
Global.pattern_images[1] = Global.patterns[0]
|
|
||||||
var pattern_right_tex := ImageTexture.new()
|
|
||||||
pattern_right_tex.create_from_image(Global.pattern_images[1], 0)
|
|
||||||
Global.fill_pattern_containers[1].get_child(0).get_child(0).texture = pattern_right_tex
|
|
||||||
Global.fill_pattern_containers[1].get_child(2).get_child(1).max_value = image_size.x - 1
|
|
||||||
Global.fill_pattern_containers[1].get_child(3).get_child(1).max_value = image_size.y - 1
|
|
||||||
|
|
||||||
|
|
||||||
func import_gpl(path : String, text : String) -> Palette:
|
func import_gpl(path : String, text : String) -> Palette:
|
||||||
|
|
|
@ -93,7 +93,7 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
|
||||||
var image := Image.new()
|
var image := Image.new()
|
||||||
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
|
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
|
||||||
new_project.brushes.append(image)
|
new_project.brushes.append(image)
|
||||||
Global.create_brush_button(image)
|
Brushes.add_project_brush(image)
|
||||||
|
|
||||||
file.close()
|
file.close()
|
||||||
if !empty_project:
|
if !empty_project:
|
||||||
|
@ -232,19 +232,13 @@ func open_old_pxo_file(file : File, new_project : Project, first_line : String)
|
||||||
guide_line = file.get_line()
|
guide_line = file.get_line()
|
||||||
|
|
||||||
# Load tool options
|
# Load tool options
|
||||||
Global.color_pickers[0].color = file.get_var()
|
file.get_var()
|
||||||
Global.color_pickers[1].color = file.get_var()
|
file.get_var()
|
||||||
Global.brush_sizes[0] = file.get_8()
|
file.get_8()
|
||||||
Global.brush_size_edits[0].value = Global.brush_sizes[0]
|
file.get_8()
|
||||||
Global.brush_sizes[1] = file.get_8()
|
|
||||||
Global.brush_size_edits[1].value = Global.brush_sizes[1]
|
|
||||||
if file_major_version == 0 and file_minor_version < 7:
|
if file_major_version == 0 and file_minor_version < 7:
|
||||||
var left_palette = file.get_var()
|
file.get_var()
|
||||||
var right_palette = file.get_var()
|
file.get_var()
|
||||||
for color in left_palette:
|
|
||||||
Global.color_pickers[0].get_picker().add_preset(color)
|
|
||||||
for color in right_palette:
|
|
||||||
Global.color_pickers[1].get_picker().add_preset(color)
|
|
||||||
|
|
||||||
# Load custom brushes
|
# Load custom brushes
|
||||||
var brush_line := file.get_line()
|
var brush_line := file.get_line()
|
||||||
|
@ -255,7 +249,7 @@ func open_old_pxo_file(file : File, new_project : Project, first_line : String)
|
||||||
var image := Image.new()
|
var image := Image.new()
|
||||||
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
|
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
|
||||||
new_project.brushes.append(image)
|
new_project.brushes.append(image)
|
||||||
Global.create_brush_button(image)
|
Brushes.add_project_brush(image)
|
||||||
brush_line = file.get_line()
|
brush_line = file.get_line()
|
||||||
|
|
||||||
if file_major_version >= 0 and file_minor_version > 6:
|
if file_major_version >= 0 and file_minor_version > 6:
|
||||||
|
|
201
src/Autoload/Tools.gd
Normal file
201
src/Autoload/Tools.gd
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
class Slot:
|
||||||
|
|
||||||
|
var name : String
|
||||||
|
var kname : String
|
||||||
|
var tool_node : Node = null
|
||||||
|
var button : int
|
||||||
|
var color : Color
|
||||||
|
|
||||||
|
var pixel_perfect := false
|
||||||
|
var horizontal_mirror := false
|
||||||
|
var vertical_mirror := false
|
||||||
|
|
||||||
|
|
||||||
|
func _init(slot_name : String) -> void:
|
||||||
|
name = slot_name
|
||||||
|
kname = name.replace(" ", "_").to_lower()
|
||||||
|
load_config()
|
||||||
|
|
||||||
|
|
||||||
|
func save_config() -> void:
|
||||||
|
var config := {
|
||||||
|
"pixel_perfect" : pixel_perfect,
|
||||||
|
"horizontal_mirror" : horizontal_mirror,
|
||||||
|
"vertical_mirror" : vertical_mirror,
|
||||||
|
}
|
||||||
|
Global.config_cache.set_value(kname, "slot", config)
|
||||||
|
|
||||||
|
|
||||||
|
func load_config() -> void:
|
||||||
|
var config = Global.config_cache.get_value(kname, "slot", {})
|
||||||
|
pixel_perfect = config.get("pixel_perfect", pixel_perfect)
|
||||||
|
horizontal_mirror = config.get("horizontal_mirror", horizontal_mirror)
|
||||||
|
vertical_mirror = config.get("vertical_mirror", vertical_mirror)
|
||||||
|
|
||||||
|
|
||||||
|
signal color_changed(color, button)
|
||||||
|
|
||||||
|
var _tools = {
|
||||||
|
"RectSelect" : "res://src/Tools/RectSelect.tscn",
|
||||||
|
"Zoom" : "res://src/Tools/Zoom.tscn",
|
||||||
|
"ColorPicker" : "res://src/Tools/ColorPicker.tscn",
|
||||||
|
"Pencil" : "res://src/Tools/Pencil.tscn",
|
||||||
|
"Eraser" : "res://src/Tools/Eraser.tscn",
|
||||||
|
"Bucket" : "res://src/Tools/Bucket.tscn",
|
||||||
|
"LightenDarken" : "res://src/Tools/LightenDarken.tscn",
|
||||||
|
}
|
||||||
|
var _slots = {}
|
||||||
|
var _panels = {}
|
||||||
|
var _tool_buttons : Node
|
||||||
|
var _active_button := -1
|
||||||
|
var _last_position := Vector2.INF
|
||||||
|
|
||||||
|
var pen_pressure := 1.0
|
||||||
|
var control := false
|
||||||
|
var shift := false
|
||||||
|
var alt := false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
yield(get_tree(), "idle_frame")
|
||||||
|
_slots[BUTTON_LEFT] = Slot.new("Left tool")
|
||||||
|
_slots[BUTTON_RIGHT] = Slot.new("Right tool")
|
||||||
|
_panels[BUTTON_LEFT] = Global.find_node_by_name(Global.control, "LeftPanelContainer")
|
||||||
|
_panels[BUTTON_RIGHT] = Global.find_node_by_name(Global.control, "RightPanelContainer")
|
||||||
|
_tool_buttons = Global.find_node_by_name(Global.control, "ToolButtons")
|
||||||
|
|
||||||
|
var value = Global.config_cache.get_value(_slots[BUTTON_LEFT].kname, "tool", "Pencil")
|
||||||
|
set_tool(value, BUTTON_LEFT)
|
||||||
|
value = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "tool", "Eraser")
|
||||||
|
set_tool(value, BUTTON_RIGHT)
|
||||||
|
value = Global.config_cache.get_value(_slots[BUTTON_LEFT].kname, "color", Color.black)
|
||||||
|
assign_color(value, BUTTON_LEFT)
|
||||||
|
value = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "color", Color.white)
|
||||||
|
assign_color(value, BUTTON_RIGHT)
|
||||||
|
|
||||||
|
update_tool_buttons()
|
||||||
|
update_tool_cursors()
|
||||||
|
|
||||||
|
|
||||||
|
func set_tool(name : String, button : int) -> void:
|
||||||
|
var slot = _slots[button]
|
||||||
|
var panel : Node = _panels[button]
|
||||||
|
var node : Node = load(_tools[name]).instance()
|
||||||
|
node.name = name
|
||||||
|
node.tool_slot = slot
|
||||||
|
slot.tool_node = node
|
||||||
|
slot.button = button
|
||||||
|
panel.add_child(slot.tool_node)
|
||||||
|
|
||||||
|
|
||||||
|
func assign_tool(name : String, button : int) -> void:
|
||||||
|
var slot = _slots[button]
|
||||||
|
var panel : Node = _panels[button]
|
||||||
|
|
||||||
|
if slot.tool_node != null:
|
||||||
|
if slot.tool_node.name == name:
|
||||||
|
return
|
||||||
|
panel.remove_child(slot.tool_node)
|
||||||
|
slot.tool_node.queue_free()
|
||||||
|
|
||||||
|
set_tool(name, button)
|
||||||
|
update_tool_buttons()
|
||||||
|
update_tool_cursors()
|
||||||
|
Global.config_cache.set_value(slot.kname, "tool", name)
|
||||||
|
|
||||||
|
|
||||||
|
func default_color() -> void:
|
||||||
|
assign_color(Color.black, BUTTON_LEFT)
|
||||||
|
assign_color(Color.white, BUTTON_RIGHT)
|
||||||
|
|
||||||
|
|
||||||
|
func swap_color() -> void:
|
||||||
|
var left = _slots[BUTTON_LEFT].color
|
||||||
|
var right = _slots[BUTTON_RIGHT].color
|
||||||
|
assign_color(right, BUTTON_LEFT)
|
||||||
|
assign_color(left, BUTTON_RIGHT)
|
||||||
|
|
||||||
|
|
||||||
|
func assign_color(color : Color, button : int) -> void:
|
||||||
|
var c : Color = _slots[button].color
|
||||||
|
if color.a == 0:
|
||||||
|
if color.r != c.r or color.g != c.g or color.b != c.b:
|
||||||
|
color.a = 1
|
||||||
|
_slots[button].color = color
|
||||||
|
Global.config_cache.set_value(_slots[button].kname, "color", color)
|
||||||
|
emit_signal("color_changed", color, button)
|
||||||
|
|
||||||
|
|
||||||
|
func get_assigned_color(button : int) -> Color:
|
||||||
|
return _slots[button].color
|
||||||
|
|
||||||
|
|
||||||
|
func update_tool_buttons() -> void:
|
||||||
|
for child in _tool_buttons.get_children():
|
||||||
|
var texture : TextureRect = child.get_child(0)
|
||||||
|
var filename = child.name.to_lower()
|
||||||
|
if _slots[BUTTON_LEFT].tool_node.name == child.name:
|
||||||
|
filename += "_l"
|
||||||
|
if _slots[BUTTON_RIGHT].tool_node.name == child.name:
|
||||||
|
filename += "_r"
|
||||||
|
filename += ".png"
|
||||||
|
Global.change_button_texturerect(texture, filename)
|
||||||
|
|
||||||
|
|
||||||
|
func update_tool_cursors() -> void:
|
||||||
|
var image = "res://assets/graphics/cursor_icons/%s_cursor.png" % _slots[BUTTON_LEFT].tool_node.name.to_lower()
|
||||||
|
Global.left_cursor_tool_texture.create_from_image(load(image), 0)
|
||||||
|
image = "res://assets/graphics/cursor_icons/%s_cursor.png" % _slots[BUTTON_RIGHT].tool_node.name.to_lower()
|
||||||
|
Global.right_cursor_tool_texture.create_from_image(load(image), 0)
|
||||||
|
|
||||||
|
|
||||||
|
func draw_indicator() -> void:
|
||||||
|
if Global.left_square_indicator_visible:
|
||||||
|
_slots[BUTTON_LEFT].tool_node.draw_indicator()
|
||||||
|
if Global.right_square_indicator_visible:
|
||||||
|
_slots[BUTTON_RIGHT].tool_node.draw_indicator()
|
||||||
|
|
||||||
|
|
||||||
|
func handle_draw(position : Vector2, event : InputEvent) -> void:
|
||||||
|
if not (Global.can_draw and Global.has_focus):
|
||||||
|
return
|
||||||
|
|
||||||
|
if event is InputEventWithModifiers:
|
||||||
|
control = event.control
|
||||||
|
shift = event.shift
|
||||||
|
alt = event.alt
|
||||||
|
|
||||||
|
if event is InputEventMouseButton:
|
||||||
|
if event.button_index in [BUTTON_LEFT, BUTTON_RIGHT]:
|
||||||
|
if event.pressed and _active_button == -1:
|
||||||
|
_active_button = event.button_index
|
||||||
|
_slots[_active_button].tool_node.draw_start(position)
|
||||||
|
elif not event.pressed and event.button_index == _active_button:
|
||||||
|
_slots[_active_button].tool_node.draw_end(position)
|
||||||
|
_active_button = -1
|
||||||
|
|
||||||
|
if event is InputEventMouseMotion:
|
||||||
|
if Engine.get_version_info().major == 3 && Engine.get_version_info().minor >= 2:
|
||||||
|
pen_pressure = event.pressure
|
||||||
|
if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.NONE:
|
||||||
|
pen_pressure = 1.0
|
||||||
|
|
||||||
|
if not position.is_equal_approx(_last_position):
|
||||||
|
_last_position = position
|
||||||
|
_slots[BUTTON_LEFT].tool_node.cursor_move(position)
|
||||||
|
_slots[BUTTON_RIGHT].tool_node.cursor_move(position)
|
||||||
|
if _active_button != -1:
|
||||||
|
_slots[_active_button].tool_node.draw_move(position)
|
||||||
|
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var text := "[%s×%s]" % [project.size.x, project.size.y]
|
||||||
|
if Global.has_focus:
|
||||||
|
text += " %s, %s" % [position.x, position.y]
|
||||||
|
if not _slots[BUTTON_LEFT].tool_node.cursor_text.empty():
|
||||||
|
text += " %s" % _slots[BUTTON_LEFT].tool_node.cursor_text
|
||||||
|
if not _slots[BUTTON_RIGHT].tool_node.cursor_text.empty():
|
||||||
|
text += " %s" % _slots[BUTTON_RIGHT].tool_node.cursor_text
|
||||||
|
Global.cursor_position_label.text = text
|
350
src/Canvas.gd
350
src/Canvas.gd
|
@ -5,17 +5,9 @@ extends Node2D
|
||||||
var location := Vector2.ZERO
|
var location := Vector2.ZERO
|
||||||
var fill_color := Color(0, 0, 0, 0)
|
var fill_color := Color(0, 0, 0, 0)
|
||||||
var current_pixel := Vector2.ZERO # pretty much same as mouse_pos, but can be accessed externally
|
var current_pixel := Vector2.ZERO # pretty much same as mouse_pos, but can be accessed externally
|
||||||
var previous_mouse_pos := Vector2.ZERO
|
|
||||||
var previous_mouse_pos_for_lines := Vector2.ZERO
|
|
||||||
var can_undo := true
|
var can_undo := true
|
||||||
var cursor_image_has_changed := false
|
var cursor_image_has_changed := false
|
||||||
var previous_action := -1
|
|
||||||
var sprite_changed_this_frame := false # for optimization purposes
|
var sprite_changed_this_frame := false # for optimization purposes
|
||||||
var is_making_line := false
|
|
||||||
var made_line := false
|
|
||||||
var is_making_selection := -1
|
|
||||||
var line_pos = []
|
|
||||||
var pen_pressure := 1.0 # For tablet pressure sensitivity
|
|
||||||
|
|
||||||
|
|
||||||
# Called when the node enters the scene tree for the first time.
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
@ -23,7 +15,6 @@ func _ready() -> void:
|
||||||
var frame : Frame = new_empty_frame(true)
|
var frame : Frame = new_empty_frame(true)
|
||||||
Global.current_project.frames.append(frame)
|
Global.current_project.frames.append(frame)
|
||||||
camera_zoom()
|
camera_zoom()
|
||||||
line_pos = [previous_mouse_pos_for_lines, previous_mouse_pos_for_lines]
|
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
|
@ -54,111 +45,40 @@ func _draw() -> void:
|
||||||
draw_grid(Global.grid_type)
|
draw_grid(Global.grid_type)
|
||||||
|
|
||||||
# Draw rectangle to indicate the pixel currently being hovered on
|
# Draw rectangle to indicate the pixel currently being hovered on
|
||||||
if Global.can_draw:
|
if Global.has_focus and Global.can_draw:
|
||||||
var mouse_pos := current_pixel
|
Tools.draw_indicator()
|
||||||
mouse_pos = mouse_pos.floor()
|
|
||||||
var visible_indicators := [Global.left_square_indicator_visible, Global.right_square_indicator_visible]
|
|
||||||
|
|
||||||
for i in range(0, 1):
|
|
||||||
if visible_indicators[i]:
|
|
||||||
if Global.current_brush_types[i] == Global.Brush_Types.PIXEL || Global.current_tools[i] == Global.Tools.LIGHTENDARKEN:
|
|
||||||
if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER || Global.current_tools[i] == Global.Tools.LIGHTENDARKEN:
|
|
||||||
var start_pos_x = mouse_pos.x - (Global.brush_sizes[i] >> 1)
|
|
||||||
var start_pos_y = mouse_pos.y - (Global.brush_sizes[i] >> 1)
|
|
||||||
draw_rect(Rect2(start_pos_x, start_pos_y, Global.brush_sizes[i], Global.brush_sizes[i]), Color.blue, false)
|
|
||||||
#Check for tile mode
|
|
||||||
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
|
|
||||||
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
|
|
||||||
var new_start_pos_x = posmod(start_pos_x,Global.current_project.size.x)
|
|
||||||
var new_start_pos_y = posmod(start_pos_y,Global.current_project.size.y)
|
|
||||||
draw_rect(Rect2(new_start_pos_x, new_start_pos_y, Global.brush_sizes[i], Global.brush_sizes[i]), Color.green, false)
|
|
||||||
if is_making_line:
|
|
||||||
var line_rect = plot_line(line_pos[1], line_pos[0])
|
|
||||||
for rect in line_rect:
|
|
||||||
draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false)
|
|
||||||
#Check for tile mode
|
|
||||||
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
|
|
||||||
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
|
|
||||||
rect.x = posmod(rect.x,Global.current_project.size.x)
|
|
||||||
rect.y = posmod(rect.y,Global.current_project.size.y)
|
|
||||||
draw_rect(Rect2(rect, Vector2.ONE), Color.green, false)
|
|
||||||
|
|
||||||
elif Global.current_brush_types[i] == Global.Brush_Types.CIRCLE || Global.current_brush_types[i] == Global.Brush_Types.FILLED_CIRCLE:
|
|
||||||
if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER:
|
|
||||||
draw_set_transform(mouse_pos, rotation, scale)
|
|
||||||
for rect in Global.left_circle_points:
|
|
||||||
draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false)
|
|
||||||
|
|
||||||
#Check for tile mode
|
|
||||||
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
|
|
||||||
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
|
|
||||||
var pos = mouse_pos.posmodv(Global.current_project.size)
|
|
||||||
if pos != mouse_pos:
|
|
||||||
draw_set_transform(pos,rotation,scale)
|
|
||||||
for rect in Global.left_circle_points:
|
|
||||||
draw_rect(Rect2(rect, Vector2.ONE), Color.green, false)
|
|
||||||
draw_set_transform(position, rotation, scale)
|
|
||||||
else:
|
|
||||||
if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER:
|
|
||||||
var custom_brush_size = Global.brush_images[i].get_size() - Vector2.ONE
|
|
||||||
var dst : Vector2 = DrawingAlgos.rectangle_center(mouse_pos, custom_brush_size)
|
|
||||||
draw_texture(Global.brush_textures[i], dst)
|
|
||||||
|
|
||||||
|
|
||||||
func _input(event : InputEvent) -> void:
|
func _input(event : InputEvent) -> void:
|
||||||
# Don't process anything below if the input isn't a mouse event, or Shift/Ctrl.
|
# Don't process anything below if the input isn't a mouse event, or Shift/Ctrl.
|
||||||
# This decreases CPU/GPU usage slightly.
|
# This decreases CPU/GPU usage slightly.
|
||||||
if not event is InputEventMouse:
|
if not event is InputEventMouse:
|
||||||
if event is InputEventKey:
|
if not event is InputEventKey:
|
||||||
if event.scancode != KEY_SHIFT && event.scancode != KEY_CONTROL:
|
|
||||||
return
|
return
|
||||||
else:
|
elif not event.scancode in [KEY_SHIFT, KEY_CONTROL]:
|
||||||
return
|
return
|
||||||
|
# elif not get_viewport_rect().has_point(event.position):
|
||||||
if (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")):
|
# return
|
||||||
made_line = false
|
|
||||||
DrawingAlgos.reset()
|
|
||||||
can_undo = true
|
|
||||||
|
|
||||||
current_pixel = get_local_mouse_position() + location
|
current_pixel = get_local_mouse_position() + location
|
||||||
|
|
||||||
if Global.has_focus:
|
if Global.has_focus:
|
||||||
update()
|
update()
|
||||||
|
|
||||||
# Godot 3.2 and above only code
|
|
||||||
if Engine.get_version_info().major == 3 && Engine.get_version_info().minor >= 2:
|
|
||||||
if event is InputEventMouseMotion:
|
|
||||||
if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.NONE:
|
|
||||||
pen_pressure = 1
|
|
||||||
else:
|
|
||||||
pen_pressure = event.pressure
|
|
||||||
|
|
||||||
sprite_changed_this_frame = false
|
sprite_changed_this_frame = false
|
||||||
var mouse_pos := current_pixel
|
|
||||||
var mouse_pos_floored := mouse_pos.floor()
|
|
||||||
var current_mouse_button := -1
|
|
||||||
var current_project : Project = Global.current_project
|
|
||||||
|
|
||||||
|
var current_project : Project = Global.current_project
|
||||||
current_project.x_min = location.x
|
current_project.x_min = location.x
|
||||||
current_project.x_max = location.x + current_project.size.x
|
current_project.x_max = location.x + current_project.size.x
|
||||||
current_project.y_min = location.y
|
current_project.y_min = location.y
|
||||||
current_project.y_max = location.y + current_project.size.y
|
current_project.y_max = location.y + current_project.size.y
|
||||||
if current_project.selected_pixels.size() != 0:
|
if not current_project.selected_rect.has_no_area():
|
||||||
current_project.x_min = max(current_project.x_min, Global.selection_rectangle.polygon[0].x)
|
current_project.x_min = max(current_project.x_min, current_project.selected_rect.position.x)
|
||||||
current_project.x_max = min(current_project.x_max, Global.selection_rectangle.polygon[2].x)
|
current_project.x_max = min(current_project.x_max, current_project.selected_rect.end.x)
|
||||||
current_project.y_min = max(current_project.y_min, Global.selection_rectangle.polygon[0].y)
|
current_project.y_min = max(current_project.y_min, current_project.selected_rect.position.y)
|
||||||
current_project.y_max = min(current_project.y_max, Global.selection_rectangle.polygon[2].y)
|
current_project.y_max = min(current_project.y_max, current_project.selected_rect.end.y)
|
||||||
|
|
||||||
if Input.is_mouse_button_pressed(BUTTON_LEFT):
|
|
||||||
current_mouse_button = Global.Mouse_Button.LEFT
|
|
||||||
|
|
||||||
elif Input.is_mouse_button_pressed(BUTTON_RIGHT):
|
|
||||||
current_mouse_button = Global.Mouse_Button.RIGHT
|
|
||||||
|
|
||||||
var current_action : int = Global.current_tools[current_mouse_button] if current_mouse_button != -1 else -1
|
|
||||||
|
|
||||||
if Global.has_focus:
|
if Global.has_focus:
|
||||||
Global.cursor_position_label.text = "[%s×%s] %s, %s" % [current_project.size.x, current_project.size.y, mouse_pos_floored.x, mouse_pos_floored.y]
|
|
||||||
if !cursor_image_has_changed:
|
if !cursor_image_has_changed:
|
||||||
cursor_image_has_changed = true
|
cursor_image_has_changed = true
|
||||||
if Global.show_left_tool_icon:
|
if Global.show_left_tool_icon:
|
||||||
|
@ -166,80 +86,13 @@ func _input(event : InputEvent) -> void:
|
||||||
if Global.show_right_tool_icon:
|
if Global.show_right_tool_icon:
|
||||||
Global.right_cursor.visible = true
|
Global.right_cursor.visible = true
|
||||||
else:
|
else:
|
||||||
Global.cursor_position_label.text = "[%s×%s]" % [current_project.size.x, current_project.size.y]
|
|
||||||
if cursor_image_has_changed:
|
if cursor_image_has_changed:
|
||||||
cursor_image_has_changed = false
|
cursor_image_has_changed = false
|
||||||
Global.left_cursor.visible = false
|
Global.left_cursor.visible = false
|
||||||
Global.right_cursor.visible = false
|
Global.right_cursor.visible = false
|
||||||
|
|
||||||
# Handle Undo/Redo
|
Tools.handle_draw(current_pixel.floor(), event)
|
||||||
var can_handle : bool = Global.can_draw && Global.has_focus && !made_line
|
|
||||||
var mouse_pressed : bool = (Input.is_action_just_pressed("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_pressed("right_mouse") && !Input.is_action_pressed("left_mouse"))
|
|
||||||
|
|
||||||
if mouse_pressed:
|
|
||||||
if can_handle || is_making_line:
|
|
||||||
if current_action != -1 && current_action != Global.Tools.COLORPICKER && current_action != Global.Tools.ZOOM:
|
|
||||||
if current_action == Global.Tools.RECTSELECT:
|
|
||||||
handle_undo("Rectangle Select")
|
|
||||||
else:
|
|
||||||
handle_undo("Draw")
|
|
||||||
elif (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")):
|
|
||||||
if can_handle || current_project.undos == current_project.undo_redo.get_version():
|
|
||||||
if previous_action != -1 && previous_action != Global.Tools.RECTSELECT && current_action != Global.Tools.COLORPICKER && current_action != Global.Tools.ZOOM:
|
|
||||||
handle_redo("Draw")
|
|
||||||
|
|
||||||
handle_tools(current_mouse_button, current_action, mouse_pos, can_handle)
|
|
||||||
|
|
||||||
if Global.can_draw && Global.has_focus && Input.is_action_just_pressed("shift") && ([Global.Tools.PENCIL, Global.Tools.ERASER, Global.Tools.LIGHTENDARKEN].has(Global.current_tools[0]) || [Global.Tools.PENCIL, Global.Tools.ERASER, Global.Tools.LIGHTENDARKEN].has(Global.current_tools[1])):
|
|
||||||
is_making_line = true
|
|
||||||
line_pos[0] = previous_mouse_pos_for_lines
|
|
||||||
elif Input.is_action_just_released("shift"):
|
|
||||||
is_making_line = false
|
|
||||||
line_pos[1] = line_pos[0]
|
|
||||||
|
|
||||||
if is_making_line:
|
|
||||||
var point0 : Vector2 = line_pos[0]
|
|
||||||
var angle := stepify(rad2deg(mouse_pos.angle_to_point(point0)), 0.01)
|
|
||||||
if Input.is_action_pressed("ctrl"):
|
|
||||||
angle = round(angle / 15) * 15
|
|
||||||
var distance : float = point0.distance_to(mouse_pos)
|
|
||||||
line_pos[1] = point0 + Vector2.RIGHT.rotated(deg2rad(angle)) * distance
|
|
||||||
else:
|
|
||||||
line_pos[1] = mouse_pos
|
|
||||||
|
|
||||||
if angle < 0:
|
|
||||||
angle = 360 + angle
|
|
||||||
Global.cursor_position_label.text += " %s°" % str(angle)
|
|
||||||
|
|
||||||
if is_making_selection != -1: # If we're making a selection
|
|
||||||
var mouse_button_string := "left_mouse" if is_making_selection == Global.Mouse_Button.LEFT else "right_mouse"
|
|
||||||
|
|
||||||
if Input.is_action_just_released(mouse_button_string): # Finish selection when button is released
|
|
||||||
var start_pos = Global.selection_rectangle.polygon[0]
|
|
||||||
var end_pos = Global.selection_rectangle.polygon[2]
|
|
||||||
if start_pos.x > end_pos.x:
|
|
||||||
var temp = end_pos.x
|
|
||||||
end_pos.x = start_pos.x
|
|
||||||
start_pos.x = temp
|
|
||||||
|
|
||||||
if start_pos.y > end_pos.y:
|
|
||||||
var temp = end_pos.y
|
|
||||||
end_pos.y = start_pos.y
|
|
||||||
start_pos.y = temp
|
|
||||||
|
|
||||||
Global.selection_rectangle.polygon[0] = start_pos
|
|
||||||
Global.selection_rectangle.polygon[1] = Vector2(end_pos.x, start_pos.y)
|
|
||||||
Global.selection_rectangle.polygon[2] = end_pos
|
|
||||||
Global.selection_rectangle.polygon[3] = Vector2(start_pos.x, end_pos.y)
|
|
||||||
|
|
||||||
for xx in range(start_pos.x, end_pos.x):
|
|
||||||
for yy in range(start_pos.y, end_pos.y):
|
|
||||||
current_project.selected_pixels.append(Vector2(xx, yy))
|
|
||||||
is_making_selection = -1
|
|
||||||
handle_redo("Rectangle Select")
|
|
||||||
|
|
||||||
previous_action = current_action
|
|
||||||
previous_mouse_pos = current_pixel
|
|
||||||
if sprite_changed_this_frame:
|
if sprite_changed_this_frame:
|
||||||
update_texture(current_project.current_layer)
|
update_texture(current_project.current_layer)
|
||||||
|
|
||||||
|
@ -284,143 +137,6 @@ func new_empty_frame(first_time := false, single_layer := false, size := Global.
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
|
|
||||||
func handle_tools(current_mouse_button : int, current_action : int, mouse_pos : Vector2, can_handle : bool) -> void:
|
|
||||||
var current_project : Project = Global.current_project
|
|
||||||
var current_cel : Cel = current_project.frames[current_project.current_frame].cels[current_project.current_layer]
|
|
||||||
var sprite : Image = current_cel.image
|
|
||||||
var mouse_pos_floored := mouse_pos.floor()
|
|
||||||
var mouse_pos_ceiled := mouse_pos.ceil()
|
|
||||||
|
|
||||||
var current_color : Color = Global.color_pickers[current_mouse_button].color
|
|
||||||
var fill_area : int = Global.fill_areas[current_mouse_button]
|
|
||||||
var ld : int = Global.ld_modes[current_mouse_button]
|
|
||||||
var ld_amount : float = Global.ld_amounts[current_mouse_button]
|
|
||||||
var color_picker_for : int = Global.color_picker_for[current_mouse_button]
|
|
||||||
var zoom_mode : int = Global.zoom_modes[current_mouse_button]
|
|
||||||
|
|
||||||
match current_action: # Handle current tool
|
|
||||||
Global.Tools.PENCIL:
|
|
||||||
pencil_and_eraser(sprite, mouse_pos, current_color, current_mouse_button, current_action)
|
|
||||||
Global.Tools.ERASER:
|
|
||||||
pencil_and_eraser(sprite, mouse_pos, Color(0, 0, 0, 0), current_mouse_button, current_action)
|
|
||||||
Global.Tools.BUCKET:
|
|
||||||
if can_handle:
|
|
||||||
var fill_with : int = Global.fill_with[current_mouse_button]
|
|
||||||
var pattern_image : Image = Global.pattern_images[current_mouse_button]
|
|
||||||
var pattern_offset : Vector2 = Global.fill_pattern_offsets[current_mouse_button]
|
|
||||||
|
|
||||||
if fill_area == Global.Fill_Area.SAME_COLOR_AREA: # Paint the specific area of the same color
|
|
||||||
var mirror_x := current_project.x_max + current_project.x_min - mouse_pos_floored.x - 1
|
|
||||||
var mirror_y := current_project.y_max + current_project.y_min - mouse_pos_floored.y - 1
|
|
||||||
var horizontal_mirror : bool = Global.horizontal_mirror[current_mouse_button]
|
|
||||||
var vertical_mirror : bool = Global.vertical_mirror[current_mouse_button]
|
|
||||||
|
|
||||||
if fill_with == Global.Fill_With.PATTERN && pattern_image: # Pattern fill
|
|
||||||
DrawingAlgos.pattern_fill(sprite, mouse_pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
|
|
||||||
if horizontal_mirror:
|
|
||||||
var pos := Vector2(mirror_x, mouse_pos.y)
|
|
||||||
DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
|
|
||||||
if vertical_mirror:
|
|
||||||
var pos := Vector2(mouse_pos.x, mirror_y)
|
|
||||||
DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
|
|
||||||
if horizontal_mirror && vertical_mirror:
|
|
||||||
var pos := Vector2(mirror_x, mirror_y)
|
|
||||||
DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
|
|
||||||
|
|
||||||
else: # Flood fill
|
|
||||||
DrawingAlgos.flood_fill(sprite, mouse_pos, sprite.get_pixelv(mouse_pos), current_color)
|
|
||||||
if horizontal_mirror:
|
|
||||||
var pos := Vector2(mirror_x, mouse_pos.y)
|
|
||||||
DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color)
|
|
||||||
if vertical_mirror:
|
|
||||||
var pos := Vector2(mouse_pos.x, mirror_y)
|
|
||||||
DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color)
|
|
||||||
if horizontal_mirror && vertical_mirror:
|
|
||||||
var pos := Vector2(mirror_x, mirror_y)
|
|
||||||
DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color)
|
|
||||||
|
|
||||||
else: # Paint all pixels of the same color
|
|
||||||
var pixel_color : Color = sprite.get_pixelv(mouse_pos)
|
|
||||||
for xx in range(current_project.x_min, current_project.x_max):
|
|
||||||
for yy in range(current_project.y_min, current_project.y_max):
|
|
||||||
var c : Color = sprite.get_pixel(xx, yy)
|
|
||||||
if c == pixel_color:
|
|
||||||
if fill_with == Global.Fill_With.PATTERN && pattern_image: # Pattern fill
|
|
||||||
pattern_image.lock()
|
|
||||||
var pattern_size := pattern_image.get_size()
|
|
||||||
var xxx : int = int(xx + pattern_offset.x) % int(pattern_size.x)
|
|
||||||
var yyy : int = int(yy + pattern_offset.y) % int(pattern_size.y)
|
|
||||||
var pattern_color : Color = pattern_image.get_pixel(xxx, yyy)
|
|
||||||
sprite.set_pixel(xx, yy, pattern_color)
|
|
||||||
pattern_image.unlock()
|
|
||||||
else:
|
|
||||||
sprite.set_pixel(xx, yy, current_color)
|
|
||||||
sprite_changed_this_frame = true
|
|
||||||
Global.Tools.LIGHTENDARKEN:
|
|
||||||
if can_handle:
|
|
||||||
var pixel_color : Color = sprite.get_pixelv(mouse_pos)
|
|
||||||
var color_changed : Color
|
|
||||||
if ld == Global.Lighten_Darken_Mode.LIGHTEN:
|
|
||||||
color_changed = pixel_color.lightened(ld_amount)
|
|
||||||
else: # Darken
|
|
||||||
color_changed = pixel_color.darkened(ld_amount)
|
|
||||||
pencil_and_eraser(sprite, mouse_pos, color_changed, current_mouse_button, current_action)
|
|
||||||
Global.Tools.RECTSELECT:
|
|
||||||
# Check SelectionRectangle.gd for more code on Rectangle Selection
|
|
||||||
if Global.can_draw && Global.has_focus:
|
|
||||||
# If we're creating a new selection
|
|
||||||
if current_project.selected_pixels.size() == 0 || !point_in_rectangle(mouse_pos_floored, Global.selection_rectangle.polygon[0] - Vector2.ONE, Global.selection_rectangle.polygon[2]):
|
|
||||||
var mouse_button_string := "left_mouse" if current_mouse_button == Global.Mouse_Button.LEFT else "right_mouse"
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed(mouse_button_string):
|
|
||||||
Global.selection_rectangle.polygon[0] = mouse_pos_floored
|
|
||||||
Global.selection_rectangle.polygon[1] = mouse_pos_floored
|
|
||||||
Global.selection_rectangle.polygon[2] = mouse_pos_floored
|
|
||||||
Global.selection_rectangle.polygon[3] = mouse_pos_floored
|
|
||||||
is_making_selection = current_mouse_button
|
|
||||||
current_project.selected_pixels.clear()
|
|
||||||
else:
|
|
||||||
if is_making_selection != -1: # If we're making a new selection...
|
|
||||||
var start_pos = Global.selection_rectangle.polygon[0]
|
|
||||||
if start_pos != mouse_pos_floored:
|
|
||||||
var end_pos := Vector2(mouse_pos_ceiled.x, mouse_pos_ceiled.y)
|
|
||||||
if mouse_pos.x < start_pos.x:
|
|
||||||
end_pos.x = mouse_pos_ceiled.x - 1
|
|
||||||
if mouse_pos.y < start_pos.y:
|
|
||||||
end_pos.y = mouse_pos_ceiled.y - 1
|
|
||||||
Global.selection_rectangle.polygon[1] = Vector2(end_pos.x, start_pos.y)
|
|
||||||
Global.selection_rectangle.polygon[2] = end_pos
|
|
||||||
Global.selection_rectangle.polygon[3] = Vector2(start_pos.x, end_pos.y)
|
|
||||||
Global.Tools.COLORPICKER:
|
|
||||||
var canvas_rect := Rect2(location, current_project.size)
|
|
||||||
if can_handle && canvas_rect.has_point(mouse_pos):
|
|
||||||
var image_data := Image.new()
|
|
||||||
image_data.copy_from(sprite)
|
|
||||||
image_data.lock()
|
|
||||||
var pixel_color : Color = image_data.get_pixelv(mouse_pos)
|
|
||||||
Global.color_pickers[color_picker_for].color = pixel_color
|
|
||||||
Global.update_custom_brush(color_picker_for)
|
|
||||||
Global.Tools.ZOOM:
|
|
||||||
if can_handle:
|
|
||||||
if zoom_mode == Global.Zoom_Mode.ZOOM_IN:
|
|
||||||
Global.camera.zoom_camera(-1)
|
|
||||||
else:
|
|
||||||
Global.camera.zoom_camera(1)
|
|
||||||
|
|
||||||
|
|
||||||
func pencil_and_eraser(sprite : Image, mouse_pos : Vector2, color : Color, current_mouse_button : int, current_action := -1) -> void:
|
|
||||||
if made_line:
|
|
||||||
return
|
|
||||||
if is_making_line:
|
|
||||||
DrawingAlgos.fill_gaps(sprite, line_pos[1], previous_mouse_pos_for_lines, color, current_mouse_button, pen_pressure, current_action)
|
|
||||||
DrawingAlgos.draw_brush(sprite, line_pos[1], color, current_mouse_button, pen_pressure, current_action)
|
|
||||||
made_line = true
|
|
||||||
else:
|
|
||||||
# Draw
|
|
||||||
DrawingAlgos.draw_brush(sprite, mouse_pos, color, current_mouse_button, pen_pressure, current_action)
|
|
||||||
DrawingAlgos.fill_gaps(sprite, mouse_pos, previous_mouse_pos, color, current_mouse_button, pen_pressure, current_action) # Fill the gaps
|
|
||||||
|
|
||||||
|
|
||||||
func handle_undo(action : String) -> void:
|
func handle_undo(action : String) -> void:
|
||||||
if !can_undo:
|
if !can_undo:
|
||||||
return
|
return
|
||||||
|
@ -442,16 +158,12 @@ func handle_undo(action : String) -> void:
|
||||||
var data = f.cels[Global.current_project.current_layer].image.data
|
var data = f.cels[Global.current_project.current_layer].image.data
|
||||||
f.cels[Global.current_project.current_layer].image.lock()
|
f.cels[Global.current_project.current_layer].image.lock()
|
||||||
Global.current_project.undo_redo.add_undo_property(f.cels[Global.current_project.current_layer].image, "data", data)
|
Global.current_project.undo_redo.add_undo_property(f.cels[Global.current_project.current_layer].image, "data", data)
|
||||||
if action == "Rectangle Select":
|
|
||||||
var selected_pixels = Global.current_project.selected_pixels.duplicate()
|
|
||||||
Global.current_project.undo_redo.add_undo_property(Global.selection_rectangle, "polygon", Global.selection_rectangle.polygon)
|
|
||||||
Global.current_project.undo_redo.add_undo_property(Global.current_project, "selected_pixels", selected_pixels)
|
|
||||||
Global.current_project.undo_redo.add_undo_method(Global, "undo", frame_index, layer_index)
|
Global.current_project.undo_redo.add_undo_method(Global, "undo", frame_index, layer_index)
|
||||||
|
|
||||||
can_undo = false
|
can_undo = false
|
||||||
|
|
||||||
|
|
||||||
func handle_redo(action : String) -> void:
|
func handle_redo(_action : String) -> void:
|
||||||
can_undo = true
|
can_undo = true
|
||||||
|
|
||||||
if Global.current_project.undos < Global.current_project.undo_redo.get_version():
|
if Global.current_project.undos < Global.current_project.undo_redo.get_version():
|
||||||
|
@ -467,9 +179,6 @@ func handle_redo(action : String) -> void:
|
||||||
frames = Global.current_project.frames
|
frames = Global.current_project.frames
|
||||||
for f in frames:
|
for f in frames:
|
||||||
Global.current_project.undo_redo.add_do_property(f.cels[Global.current_project.current_layer].image, "data", f.cels[Global.current_project.current_layer].image.data)
|
Global.current_project.undo_redo.add_do_property(f.cels[Global.current_project.current_layer].image, "data", f.cels[Global.current_project.current_layer].image.data)
|
||||||
if action == "Rectangle Select":
|
|
||||||
Global.current_project.undo_redo.add_do_property(Global.selection_rectangle, "polygon", Global.selection_rectangle.polygon)
|
|
||||||
Global.current_project.undo_redo.add_do_property(Global.current_project, "selected_pixels", Global.current_project.selected_pixels)
|
|
||||||
Global.current_project.undo_redo.add_do_method(Global, "redo", frame_index, layer_index)
|
Global.current_project.undo_redo.add_do_method(Global, "redo", frame_index, layer_index)
|
||||||
Global.current_project.undo_redo.commit_action()
|
Global.current_project.undo_redo.commit_action()
|
||||||
|
|
||||||
|
@ -561,34 +270,3 @@ func draw_grid(grid_type : int) -> void:
|
||||||
for x in range(0, size.x, Global.grid_height * 2):
|
for x in range(0, size.x, Global.grid_height * 2):
|
||||||
var yy2 = (size.x - x) * tan(deg2rad(26.565)) # 30 degrees
|
var yy2 = (size.x - x) * tan(deg2rad(26.565)) # 30 degrees
|
||||||
draw_line(Vector2(x, size.y), Vector2(size.x, size.y - yy2), Global.grid_color)
|
draw_line(Vector2(x, size.y), Vector2(size.x, size.y - yy2), Global.grid_color)
|
||||||
|
|
||||||
|
|
||||||
# Bresenham's Algorithm
|
|
||||||
# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
|
|
||||||
func plot_line(end_pos : Vector2, start_pos : Vector2) -> Array:
|
|
||||||
start_pos = start_pos.floor()
|
|
||||||
end_pos = end_pos.floor()
|
|
||||||
var line_points := []
|
|
||||||
var dx := int(abs(end_pos.x - start_pos.x))
|
|
||||||
var dy := int(-abs(end_pos.y - start_pos.y))
|
|
||||||
var err := dx + dy
|
|
||||||
var e2 := err << 1 # err * 2
|
|
||||||
var sx = 1 if start_pos.x < end_pos.x else -1
|
|
||||||
var sy = 1 if start_pos.y < end_pos.y else -1
|
|
||||||
var x = start_pos.x
|
|
||||||
var y = start_pos.y
|
|
||||||
while !(x == end_pos.x && y == end_pos.y):
|
|
||||||
line_points.append(Vector2(x, y))
|
|
||||||
e2 = err << 1
|
|
||||||
if e2 >= dy:
|
|
||||||
err += dy
|
|
||||||
x += sx
|
|
||||||
if e2 <= dx:
|
|
||||||
err += dx
|
|
||||||
y += sy
|
|
||||||
return line_points
|
|
||||||
|
|
||||||
|
|
||||||
# Checks if a point is inside a rectangle
|
|
||||||
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
|
||||||
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y
|
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
class_name Drawer
|
class_name Drawer
|
||||||
|
|
||||||
|
|
||||||
|
class ColorOp:
|
||||||
|
var strength := 1.0
|
||||||
|
|
||||||
|
|
||||||
|
func process(src: Color, _dst: Color) -> Color:
|
||||||
|
return src
|
||||||
|
|
||||||
|
|
||||||
class SimpleDrawer:
|
class SimpleDrawer:
|
||||||
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
|
func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void:
|
||||||
_sprite.set_pixel(_pos.x, _pos.y, _new_color)
|
var color_old := image.get_pixelv(position)
|
||||||
|
var color_new := op.process(color, color_old)
|
||||||
|
if not color_new.is_equal_approx(color_old):
|
||||||
|
image.set_pixelv(position, color_new)
|
||||||
|
|
||||||
|
|
||||||
class PixelPerfectDrawer:
|
class PixelPerfectDrawer:
|
||||||
|
@ -15,9 +27,10 @@ class PixelPerfectDrawer:
|
||||||
last_pixels = [null, null]
|
last_pixels = [null, null]
|
||||||
|
|
||||||
|
|
||||||
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
|
func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void:
|
||||||
last_pixels.push_back([_pos, _sprite.get_pixel(_pos.x, _pos.y)])
|
var color_old = image.get_pixelv(position)
|
||||||
_sprite.set_pixel(_pos.x, _pos.y, _new_color)
|
last_pixels.push_back([position, color_old])
|
||||||
|
image.set_pixelv(position, op.process(color, color_old))
|
||||||
|
|
||||||
var corner = last_pixels.pop_front()
|
var corner = last_pixels.pop_front()
|
||||||
var neighbour = last_pixels[0]
|
var neighbour = last_pixels[0]
|
||||||
|
@ -25,14 +38,15 @@ class PixelPerfectDrawer:
|
||||||
if corner == null or neighbour == null:
|
if corner == null or neighbour == null:
|
||||||
return
|
return
|
||||||
|
|
||||||
if _pos - corner[0] in corners and _pos - neighbour[0] in neighbours:
|
if position - corner[0] in corners and position - neighbour[0] in neighbours:
|
||||||
_sprite.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
|
image.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
|
||||||
last_pixels[0] = corner
|
last_pixels[0] = corner
|
||||||
|
|
||||||
|
|
||||||
var pixel_perfect := false setget set_pixel_perfect
|
var pixel_perfect := false setget set_pixel_perfect
|
||||||
var h_mirror := false
|
var horizontal_mirror := false
|
||||||
var v_mirror := false
|
var vertical_mirror := false
|
||||||
|
var color_op := ColorOp.new()
|
||||||
|
|
||||||
var simple_drawer := SimpleDrawer.new()
|
var simple_drawer := SimpleDrawer.new()
|
||||||
var pixel_perfect_drawers = [PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new()]
|
var pixel_perfect_drawers = [PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new()]
|
||||||
|
@ -52,14 +66,14 @@ func set_pixel_perfect(value: bool) -> void:
|
||||||
drawers = [simple_drawer, simple_drawer, simple_drawer, simple_drawer]
|
drawers = [simple_drawer, simple_drawer, simple_drawer, simple_drawer]
|
||||||
|
|
||||||
|
|
||||||
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
|
func set_pixel(image: Image, position: Vector2, color: Color) -> void:
|
||||||
var mirror_x = Global.current_project.x_max + Global.current_project.x_min - _pos.x - 1
|
var mirror_x = Global.current_project.x_max + Global.current_project.x_min - position.x - 1
|
||||||
var mirror_y = Global.current_project.y_max + Global.current_project.y_min - _pos.y - 1
|
var mirror_y = Global.current_project.y_max + Global.current_project.y_min - position.y - 1
|
||||||
|
|
||||||
drawers[0].set_pixel(_sprite, _pos, _new_color)
|
drawers[0].set_pixel(image, position, color, color_op)
|
||||||
if h_mirror:
|
if horizontal_mirror:
|
||||||
drawers[1].set_pixel(_sprite, Vector2(mirror_x, _pos.y), _new_color)
|
drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op)
|
||||||
if v_mirror:
|
if vertical_mirror:
|
||||||
drawers[2].set_pixel(_sprite, Vector2(mirror_x, mirror_y), _new_color)
|
drawers[2].set_pixel(image, Vector2(mirror_x, mirror_y), color, color_op)
|
||||||
if v_mirror:
|
if vertical_mirror:
|
||||||
drawers[3].set_pixel(_sprite, Vector2(_pos.x, mirror_y), _new_color)
|
drawers[3].set_pixel(image, Vector2(position.x, mirror_y), color, color_op)
|
||||||
|
|
|
@ -16,12 +16,13 @@ var guides := [] # Array of Guides
|
||||||
|
|
||||||
var brushes := [] # Array of Images
|
var brushes := [] # Array of Images
|
||||||
|
|
||||||
var selected_pixels := []
|
|
||||||
var x_min := 0
|
var x_min := 0
|
||||||
var x_max := 64
|
var x_max := 64
|
||||||
var y_min := 0
|
var y_min := 0
|
||||||
var y_max := 64
|
var y_max := 64
|
||||||
|
|
||||||
|
var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect
|
||||||
|
|
||||||
# For every camera (currently there are 3)
|
# 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
|
var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2
|
||||||
var cameras_offset := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO] # Array of Vector2
|
var cameras_offset := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO] # Array of Vector2
|
||||||
|
@ -39,6 +40,11 @@ func _init(_frames := [], _name := tr("untitled")) -> void:
|
||||||
OpenSave.backup_save_paths.append("")
|
OpenSave.backup_save_paths.append("")
|
||||||
|
|
||||||
|
|
||||||
|
func _set_selected_rect(value : Rect2) -> void:
|
||||||
|
selected_rect = value
|
||||||
|
Global.selection_rectangle.set_rect(value)
|
||||||
|
|
||||||
|
|
||||||
func change_project() -> void:
|
func change_project() -> void:
|
||||||
# Remove old nodes
|
# Remove old nodes
|
||||||
for container in Global.layers_container.get_children():
|
for container in Global.layers_container.get_children():
|
||||||
|
@ -94,16 +100,7 @@ func change_project() -> void:
|
||||||
self.animation_tags = animation_tags
|
self.animation_tags = animation_tags
|
||||||
|
|
||||||
# Change the selection rectangle
|
# Change the selection rectangle
|
||||||
if selected_pixels.size() != 0:
|
Global.selection_rectangle.set_rect(selected_rect)
|
||||||
Global.selection_rectangle.polygon[0] = Vector2(x_min, y_min)
|
|
||||||
Global.selection_rectangle.polygon[1] = Vector2(x_max, y_min)
|
|
||||||
Global.selection_rectangle.polygon[2] = Vector2(x_max, y_max)
|
|
||||||
Global.selection_rectangle.polygon[3] = Vector2(x_min, y_max)
|
|
||||||
else:
|
|
||||||
Global.selection_rectangle.polygon[0] = Vector2.ZERO
|
|
||||||
Global.selection_rectangle.polygon[1] = Vector2.ZERO
|
|
||||||
Global.selection_rectangle.polygon[2] = Vector2.ZERO
|
|
||||||
Global.selection_rectangle.polygon[3] = Vector2.ZERO
|
|
||||||
|
|
||||||
# Change the guides
|
# Change the guides
|
||||||
for guide in Global.canvas.get_children():
|
for guide in Global.canvas.get_children():
|
||||||
|
@ -114,11 +111,9 @@ func change_project() -> void:
|
||||||
guide.visible = false
|
guide.visible = false
|
||||||
|
|
||||||
# Change the project brushes
|
# Change the project brushes
|
||||||
for child in Global.project_brush_container.get_children():
|
Brushes.clear_project_brush()
|
||||||
child.queue_free()
|
|
||||||
|
|
||||||
for brush in brushes:
|
for brush in brushes:
|
||||||
Global.create_brush_button(brush)
|
Brushes.add_project_brush(brush)
|
||||||
|
|
||||||
var cameras = [Global.camera, Global.camera2, Global.camera_preview]
|
var cameras = [Global.camera, Global.camera2, Global.camera_preview]
|
||||||
var i := 0
|
var i := 0
|
||||||
|
|
|
@ -19,9 +19,6 @@ func _ready() -> void:
|
||||||
Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order())
|
Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order())
|
||||||
Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order())
|
Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order())
|
||||||
|
|
||||||
Global.color_pickers[0].get_picker().presets_visible = false
|
|
||||||
Global.color_pickers[1].get_picker().presets_visible = false
|
|
||||||
|
|
||||||
$QuitAndSaveDialog.add_button("Save & Exit", false, "Save")
|
$QuitAndSaveDialog.add_button("Save & Exit", false, "Save")
|
||||||
$QuitAndSaveDialog.get_ok().text = "Exit without saving"
|
$QuitAndSaveDialog.get_ok().text = "Exit without saving"
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,8 @@ func open(palette : String) -> void:
|
||||||
self.popup_centered()
|
self.popup_centered()
|
||||||
Global.dialog_open(true)
|
Global.dialog_open(true)
|
||||||
|
|
||||||
left_color_button.modulate = Global.color_pickers[0].color
|
left_color_button.modulate = Tools.get_assigned_color(BUTTON_LEFT)
|
||||||
right_color_button.modulate = Global.color_pickers[1].color
|
right_color_button.modulate = Tools.get_assigned_color(BUTTON_RIGHT)
|
||||||
|
|
||||||
|
|
||||||
func _display_palette() -> void:
|
func _display_palette() -> void:
|
||||||
|
@ -177,12 +177,12 @@ func _refresh_hint_tooltip(_index : int) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _on_LeftColor_pressed() -> void:
|
func _on_LeftColor_pressed() -> void:
|
||||||
color_picker.color = Global.color_pickers[0].color
|
color_picker.color = Tools.get_assigned_color(BUTTON_LEFT)
|
||||||
_on_EditPaletteColorPicker_color_changed(color_picker.color)
|
_on_EditPaletteColorPicker_color_changed(color_picker.color)
|
||||||
|
|
||||||
|
|
||||||
func _on_RightColor_pressed() -> void:
|
func _on_RightColor_pressed() -> void:
|
||||||
color_picker.color = Global.color_pickers[1].color
|
color_picker.color = Tools.get_assigned_color(BUTTON_RIGHT)
|
||||||
_on_EditPaletteColorPicker_color_changed(color_picker.color)
|
_on_EditPaletteColorPicker_color_changed(color_picker.color)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -220,11 +220,9 @@ func on_color_select(index : int) -> void:
|
||||||
var color : Color = Global.palettes[current_palette].get_color(index)
|
var color : Color = Global.palettes[current_palette].get_color(index)
|
||||||
|
|
||||||
if Input.is_action_just_pressed("left_mouse"):
|
if Input.is_action_just_pressed("left_mouse"):
|
||||||
Global.color_pickers[0].color = color
|
Tools.assign_color(color, BUTTON_LEFT)
|
||||||
Global.update_custom_brush(0)
|
|
||||||
elif Input.is_action_just_pressed("right_mouse"):
|
elif Input.is_action_just_pressed("right_mouse"):
|
||||||
Global.color_pickers[1].color = color
|
Tools.assign_color(color, BUTTON_RIGHT)
|
||||||
Global.update_custom_brush(1)
|
|
||||||
|
|
||||||
|
|
||||||
func _load_palettes() -> void:
|
func _load_palettes() -> void:
|
||||||
|
|
|
@ -1,144 +1,156 @@
|
||||||
extends Polygon2D
|
extends Polygon2D
|
||||||
|
|
||||||
var img : Image
|
|
||||||
var tex : ImageTexture
|
var _selected_rect := Rect2(0, 0, 0, 0)
|
||||||
var is_dragging := false
|
var _clipped_rect := Rect2(0, 0, 0, 0)
|
||||||
var move_pixels := false
|
var _move_image := Image.new()
|
||||||
var diff_x := 0.0
|
var _move_texture := ImageTexture.new()
|
||||||
var diff_y := 0.0
|
var _clear_image := Image.new()
|
||||||
var orig_x := 0.0
|
var _move_pixel := false
|
||||||
var orig_y := 0.0
|
var _clipboard := Image.new()
|
||||||
var orig_colors := []
|
var _undo_data := {}
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
img = Image.new()
|
_clear_image.create(1, 1, false, Image.FORMAT_RGBA8)
|
||||||
img.create(1, 1, false, Image.FORMAT_RGBA8)
|
_clear_image.fill(Color(0, 0, 0, 0))
|
||||||
img.lock()
|
|
||||||
tex = ImageTexture.new()
|
|
||||||
tex.create_from_image(img, 0)
|
|
||||||
|
|
||||||
|
|
||||||
func _process(_delta : float) -> void:
|
|
||||||
if Global.current_project.layers[Global.current_project.current_layer].locked:
|
|
||||||
return
|
|
||||||
var mouse_pos: Vector2 = get_local_mouse_position() - Global.canvas.location
|
|
||||||
var mouse_pos_floored := mouse_pos.floor()
|
|
||||||
var start_pos := polygon[0]
|
|
||||||
var end_pos := polygon[2]
|
|
||||||
var current_layer_index : int = Global.current_project.current_layer
|
|
||||||
var layer : Image = Global.current_project.frames[Global.current_project.current_frame].cels[current_layer_index].image
|
|
||||||
|
|
||||||
if end_pos == start_pos:
|
|
||||||
visible = false
|
|
||||||
else:
|
|
||||||
visible = true
|
|
||||||
|
|
||||||
if Global.can_draw and Global.has_focus and point_in_rectangle(mouse_pos, polygon[0], polygon[2]) and Global.current_project.selected_pixels.size() > 0 and (Global.current_tools[0] == Global.Tools.RECTSELECT or Global.current_tools[1] == Global.Tools.RECTSELECT):
|
|
||||||
get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_MOVE
|
|
||||||
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
|
|
||||||
if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_just_pressed("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_just_pressed("right_mouse")):
|
|
||||||
# Begin dragging
|
|
||||||
is_dragging = true
|
|
||||||
if Input.is_key_pressed(KEY_SHIFT):
|
|
||||||
move_pixels = true
|
|
||||||
else:
|
|
||||||
move_pixels = false
|
|
||||||
img.fill(Color(0, 0, 0, 0))
|
|
||||||
diff_x = end_pos.x - mouse_pos_floored.x
|
|
||||||
diff_y = end_pos.y - mouse_pos_floored.y
|
|
||||||
orig_x = start_pos.x - mouse_pos_floored.x
|
|
||||||
orig_y = start_pos.y - mouse_pos_floored.y
|
|
||||||
if move_pixels:
|
|
||||||
img.unlock()
|
|
||||||
img.resize(polygon[2].x - polygon[0].x, polygon[2].y - polygon[0].y, 0)
|
|
||||||
img.lock()
|
|
||||||
for i in range(Global.current_project.selected_pixels.size()):
|
|
||||||
var curr_px = Global.current_project.selected_pixels[i]
|
|
||||||
if point_in_rectangle(curr_px, Global.canvas.location - Vector2.ONE, Global.current_project.size):
|
|
||||||
orig_colors.append(layer.get_pixelv(curr_px)) # Color of pixel
|
|
||||||
var px = curr_px - Global.current_project.selected_pixels[0]
|
|
||||||
img.set_pixelv(px, orig_colors[i])
|
|
||||||
layer.set_pixelv(curr_px, Color(0, 0, 0, 0))
|
|
||||||
else: # If part of selection is outside canvas
|
|
||||||
orig_colors.append(Color(0, 0, 0, 0))
|
|
||||||
Global.canvas.update_texture(current_layer_index)
|
|
||||||
tex.create_from_image(img, 0)
|
|
||||||
update()
|
|
||||||
|
|
||||||
else:
|
|
||||||
get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_CROSS
|
|
||||||
|
|
||||||
if is_dragging:
|
|
||||||
if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_pressed("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_pressed("right_mouse")):
|
|
||||||
# Drag
|
|
||||||
start_pos.x = orig_x + mouse_pos_floored.x
|
|
||||||
end_pos.x = diff_x + mouse_pos_floored.x
|
|
||||||
|
|
||||||
start_pos.y = orig_y + mouse_pos_floored.y
|
|
||||||
end_pos.y = diff_y + mouse_pos_floored.y
|
|
||||||
polygon[0] = start_pos
|
|
||||||
polygon[1] = Vector2(end_pos.x, start_pos.y)
|
|
||||||
polygon[2] = end_pos
|
|
||||||
polygon[3] = Vector2(start_pos.x, end_pos.y)
|
|
||||||
|
|
||||||
if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_just_released("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_just_released("right_mouse")):
|
|
||||||
# Release Drag
|
|
||||||
is_dragging = false
|
|
||||||
if move_pixels:
|
|
||||||
for i in range(orig_colors.size()):
|
|
||||||
if orig_colors[i].a > 0:
|
|
||||||
var px = polygon[0] + Global.current_project.selected_pixels[i] - Global.current_project.selected_pixels[0]
|
|
||||||
if point_in_rectangle(px, Global.canvas.location - Vector2.ONE, Global.current_project.size):
|
|
||||||
layer.set_pixelv(px, orig_colors[i])
|
|
||||||
Global.canvas.update_texture(current_layer_index)
|
|
||||||
img.fill(Color(0, 0, 0, 0))
|
|
||||||
tex.create_from_image(img, 0)
|
|
||||||
update()
|
|
||||||
|
|
||||||
orig_colors.clear()
|
|
||||||
Global.current_project.selected_pixels.clear()
|
|
||||||
for xx in range(start_pos.x, end_pos.x):
|
|
||||||
for yy in range(start_pos.y, end_pos.y):
|
|
||||||
Global.current_project.selected_pixels.append(Vector2(xx, yy))
|
|
||||||
|
|
||||||
Global.canvas.handle_redo("Rectangle Select") # Redo
|
|
||||||
|
|
||||||
if Global.current_project.selected_pixels.size() > 0:
|
|
||||||
# Handle copy
|
|
||||||
if Input.is_action_just_pressed("copy"):
|
|
||||||
# Save as custom brush
|
|
||||||
var brush_img := Image.new()
|
|
||||||
brush_img = layer.get_rect(Rect2(polygon[0], polygon[2] - polygon[0]))
|
|
||||||
if brush_img.is_invisible():
|
|
||||||
return
|
|
||||||
brush_img = brush_img.get_rect(brush_img.get_used_rect()) # Save only the visible pixels
|
|
||||||
Global.current_project.brushes.append(brush_img)
|
|
||||||
Global.create_brush_button(brush_img)
|
|
||||||
|
|
||||||
# Have it in the clipboard so it can be pasted later
|
|
||||||
Global.image_clipboard = layer.get_rect(Rect2(polygon[0], polygon[2] - polygon[0]))
|
|
||||||
|
|
||||||
# Handle paste
|
|
||||||
if Input.is_action_just_pressed("paste") && Global.image_clipboard.get_size() > Vector2.ZERO:
|
|
||||||
Global.canvas.handle_undo("Draw")
|
|
||||||
layer.blend_rect(Global.image_clipboard, Rect2(Vector2.ZERO, polygon[2]-polygon[0]), polygon[0])
|
|
||||||
layer.lock()
|
|
||||||
Global.canvas.handle_redo("Draw")
|
|
||||||
|
|
||||||
if Input.is_action_just_pressed("delete"):
|
|
||||||
Global.canvas.handle_undo("Draw")
|
|
||||||
for xx in range(start_pos.x, end_pos.x):
|
|
||||||
for yy in range(start_pos.y, end_pos.y):
|
|
||||||
if point_in_rectangle(Vector2(xx, yy), Global.canvas.location - Vector2.ONE, Global.canvas.location + Global.current_project.size):
|
|
||||||
layer.set_pixel(xx, yy, Color(0, 0, 0, 0))
|
|
||||||
Global.canvas.handle_redo("Draw")
|
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
if img.get_size() == polygon[2] - polygon[0]:
|
if _move_pixel:
|
||||||
draw_texture(tex, polygon[0], Color(1, 1, 1, 0.5))
|
draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5))
|
||||||
|
|
||||||
|
|
||||||
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
|
func has_point(position : Vector2) -> bool:
|
||||||
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y
|
return _selected_rect.has_point(position)
|
||||||
|
|
||||||
|
|
||||||
|
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 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 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 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)
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
|
130
src/Tools/Base.gd
Normal file
130
src/Tools/Base.gd
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
extends VBoxContainer
|
||||||
|
|
||||||
|
|
||||||
|
var kname : String
|
||||||
|
var tool_slot : Tools.Slot = null
|
||||||
|
var cursor_text := ""
|
||||||
|
|
||||||
|
var _cursor := Vector2.INF
|
||||||
|
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
kname = name.replace(" ", "_").to_lower()
|
||||||
|
$Label.text = tool_slot.name
|
||||||
|
|
||||||
|
yield(get_tree(), "idle_frame")
|
||||||
|
load_config()
|
||||||
|
$PixelPerfect.pressed = tool_slot.pixel_perfect
|
||||||
|
$Mirror/Horizontal.pressed = tool_slot.horizontal_mirror
|
||||||
|
$Mirror/Vertical.pressed = tool_slot.vertical_mirror
|
||||||
|
|
||||||
|
|
||||||
|
func _on_PixelPerfect_toggled(button_pressed : bool):
|
||||||
|
tool_slot.pixel_perfect = button_pressed
|
||||||
|
tool_slot.save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Horizontal_toggled(button_pressed : bool):
|
||||||
|
tool_slot.horizontal_mirror = button_pressed
|
||||||
|
tool_slot.save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Vertical_toggled(button_pressed : bool):
|
||||||
|
tool_slot.vertical_mirror = button_pressed
|
||||||
|
tool_slot.save_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 cursor_move(position : Vector2) -> void:
|
||||||
|
_cursor = position
|
||||||
|
|
||||||
|
|
||||||
|
func draw_indicator() -> void:
|
||||||
|
var rect := Rect2(_cursor, Vector2.ONE)
|
||||||
|
Global.canvas.draw_rect(rect, Color.blue, false)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_draw_rect() -> Rect2:
|
||||||
|
var x_min : int = Global.current_project.x_min
|
||||||
|
var x_max : int = Global.current_project.x_max
|
||||||
|
var y_min : int = Global.current_project.y_min
|
||||||
|
var y_max : int = Global.current_project.y_max
|
||||||
|
return Rect2(x_min, y_min, x_max - x_min, y_max - y_min)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_tile_mode_rect() -> Rect2:
|
||||||
|
return Rect2(-Global.current_project.size, Global.current_project.size * 3)
|
||||||
|
|
||||||
|
|
||||||
|
func _get_draw_image() -> Image:
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
return project.frames[project.current_frame].cels[project.current_layer].image
|
||||||
|
|
||||||
|
|
||||||
|
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 _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])
|
77
src/Tools/Base.tscn
Normal file
77
src/Tools/Base.tscn
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
[gd_scene load_steps=6 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Base.gd" type="Script" id=1]
|
||||||
|
[ext_resource path="res://assets/graphics/dark_themes/tools/horizontal_mirror_on.png" type="Texture" id=2]
|
||||||
|
[ext_resource path="res://assets/graphics/dark_themes/tools/horizontal_mirror_off.png" type="Texture" id=3]
|
||||||
|
[ext_resource path="res://assets/graphics/dark_themes/tools/vertical_mirror_on.png" type="Texture" id=4]
|
||||||
|
[ext_resource path="res://assets/graphics/dark_themes/tools/vertical_mirror_off.png" type="Texture" id=5]
|
||||||
|
|
||||||
|
[node name="ToolOptions" type="VBoxContainer"]
|
||||||
|
margin_left = 7.0
|
||||||
|
margin_top = 7.0
|
||||||
|
margin_right = 123.0
|
||||||
|
margin_bottom = 65.0
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
text = "Tool"
|
||||||
|
align = 1
|
||||||
|
autowrap = true
|
||||||
|
|
||||||
|
[node name="PixelPerfect" type="CheckBox" parent="."]
|
||||||
|
margin_left = 4.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 112.0
|
||||||
|
margin_bottom = 42.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Pixel Perfect"
|
||||||
|
align = 1
|
||||||
|
|
||||||
|
[node name="EmptySpacer" type="Control" parent="."]
|
||||||
|
margin_top = 46.0
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 58.0
|
||||||
|
rect_min_size = Vector2( 0, 12 )
|
||||||
|
|
||||||
|
[node name="Mirror" type="HBoxContainer" parent="."]
|
||||||
|
margin_top = 62.0
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 79.0
|
||||||
|
custom_constants/separation = 44
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Horizontal" type="TextureButton" parent="Mirror" groups=[
|
||||||
|
"UIButtons",
|
||||||
|
]]
|
||||||
|
margin_left = 20.0
|
||||||
|
margin_right = 35.0
|
||||||
|
margin_bottom = 17.0
|
||||||
|
hint_tooltip = "Enable horizontal mirrored drawing"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
toggle_mode = true
|
||||||
|
texture_normal = ExtResource( 3 )
|
||||||
|
texture_pressed = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="Vertical" type="TextureButton" parent="Mirror" groups=[
|
||||||
|
"UIButtons",
|
||||||
|
]]
|
||||||
|
margin_left = 79.0
|
||||||
|
margin_right = 96.0
|
||||||
|
margin_bottom = 17.0
|
||||||
|
hint_tooltip = "Enable vertical mirrored drawing"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
toggle_mode = true
|
||||||
|
texture_normal = ExtResource( 5 )
|
||||||
|
texture_pressed = ExtResource( 4 )
|
||||||
|
[connection signal="toggled" from="PixelPerfect" to="." method="_on_PixelPerfect_toggled"]
|
||||||
|
[connection signal="toggled" from="Mirror/Horizontal" to="." method="_on_Horizontal_toggled"]
|
||||||
|
[connection signal="toggled" from="Mirror/Vertical" to="." method="_on_Vertical_toggled"]
|
204
src/Tools/Bucket.gd
Normal file
204
src/Tools/Bucket.gd
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
extends "res://src/Tools/Base.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _pattern : Patterns.Pattern
|
||||||
|
var _fill_area := 0
|
||||||
|
var _fill_with := 0
|
||||||
|
var _offset_x := 0
|
||||||
|
var _offset_y := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
update_pattern()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_FillAreaOptions_item_selected(index : int) -> void:
|
||||||
|
_fill_area = index
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_FillWithOptions_item_selected(index : int) -> void:
|
||||||
|
_fill_with = index
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_PatternType_pressed():
|
||||||
|
Global.patterns_popup.connect("pattern_selected", self, "_on_Pattern_selected", [], CONNECT_ONESHOT)
|
||||||
|
Global.patterns_popup.popup(Rect2($FillPattern/Type.rect_global_position, Vector2(226, 72)))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Pattern_selected(pattern : Patterns.Pattern) -> void:
|
||||||
|
_pattern = pattern
|
||||||
|
update_pattern()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_PatternOffsetX_value_changed(value : float):
|
||||||
|
_offset_x = int(value)
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_PatternOffsetY_value_changed(value : float):
|
||||||
|
_offset_y = int(value)
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func get_config() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"pattern_index" : _pattern.index,
|
||||||
|
"fill_area" : _fill_area,
|
||||||
|
"fill_with" : _fill_with,
|
||||||
|
"offset_x" : _offset_x,
|
||||||
|
"offset_y" : _offset_y,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func set_config(config : Dictionary) -> void:
|
||||||
|
var index = config.get("pattern_index", _pattern.index)
|
||||||
|
_pattern = Global.patterns_popup.get_pattern(index)
|
||||||
|
_fill_area = config.get("fill_area", _fill_area)
|
||||||
|
_fill_with = config.get("fill_with", _fill_with)
|
||||||
|
_offset_x = config.get("offset_x", _offset_x)
|
||||||
|
_offset_y = config.get("offset_y", _offset_y)
|
||||||
|
update_pattern()
|
||||||
|
|
||||||
|
|
||||||
|
func update_config() -> void:
|
||||||
|
$FillAreaOptions.selected = _fill_area
|
||||||
|
$FillWithOptions.selected = _fill_with
|
||||||
|
$Mirror.visible = _fill_area == 0
|
||||||
|
$FillPattern.visible = _fill_with == 1
|
||||||
|
$FillPattern/XOffset/OffsetX.value = _offset_x
|
||||||
|
$FillPattern/YOffset/OffsetY.value = _offset_y
|
||||||
|
|
||||||
|
|
||||||
|
func update_pattern() -> void:
|
||||||
|
if _pattern == null:
|
||||||
|
_pattern = Global.patterns_popup.default_pattern
|
||||||
|
var tex := ImageTexture.new()
|
||||||
|
tex.create_from_image(_pattern.image, 0)
|
||||||
|
$FillPattern/Type/Texture.texture = tex
|
||||||
|
var size := _pattern.image.get_size()
|
||||||
|
$FillPattern/XOffset/OffsetX.max_value = size.x - 1
|
||||||
|
$FillPattern/YOffset/OffsetY.max_value = size.y - 1
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(position : Vector2) -> void:
|
||||||
|
if not _get_draw_rect().has_point(position):
|
||||||
|
return
|
||||||
|
var undo_data = _get_undo_data()
|
||||||
|
if _fill_area == 0:
|
||||||
|
fill_in_area(position)
|
||||||
|
else:
|
||||||
|
fill_in_color(position)
|
||||||
|
commit_undo("Draw", undo_data)
|
||||||
|
|
||||||
|
|
||||||
|
func draw_move(_position : Vector2) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func draw_end(_position : Vector2) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func fill_in_color(position : Vector2) -> void:
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var image := _get_draw_image()
|
||||||
|
var color := image.get_pixelv(position)
|
||||||
|
if _fill_with == 0 or _pattern == null:
|
||||||
|
if tool_slot.color.is_equal_approx(color):
|
||||||
|
return
|
||||||
|
|
||||||
|
image.lock()
|
||||||
|
for y in range(project.y_min, project.y_max):
|
||||||
|
for x in range(project.x_min, project.x_max):
|
||||||
|
if image.get_pixel(x, y).is_equal_approx(color):
|
||||||
|
_set_pixel(image, x, y, tool_slot.color)
|
||||||
|
|
||||||
|
|
||||||
|
func fill_in_area(position : Vector2) -> void:
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var mirror_x := project.x_max + project.x_min - position.x - 1
|
||||||
|
var mirror_y := project.y_max + project.y_min - position.y - 1
|
||||||
|
|
||||||
|
_flood_fill(position)
|
||||||
|
if tool_slot.horizontal_mirror:
|
||||||
|
_flood_fill(Vector2(mirror_x, position.y))
|
||||||
|
if tool_slot.vertical_mirror:
|
||||||
|
_flood_fill(Vector2(mirror_x, mirror_y))
|
||||||
|
if tool_slot.vertical_mirror:
|
||||||
|
_flood_fill(Vector2(position.x, mirror_y))
|
||||||
|
|
||||||
|
|
||||||
|
func _flood_fill(position : Vector2) -> void:
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var image := _get_draw_image()
|
||||||
|
var color := image.get_pixelv(position)
|
||||||
|
if _fill_with == 0 or _pattern == null:
|
||||||
|
if tool_slot.color.is_equal_approx(color):
|
||||||
|
return
|
||||||
|
|
||||||
|
image.lock()
|
||||||
|
var processed := BitMap.new()
|
||||||
|
processed.create(image.get_size())
|
||||||
|
var q = [position]
|
||||||
|
for n in q:
|
||||||
|
if processed.get_bit(n):
|
||||||
|
continue
|
||||||
|
var west : Vector2 = n
|
||||||
|
var east : Vector2 = n
|
||||||
|
while west.x >= project.x_min && image.get_pixelv(west).is_equal_approx(color):
|
||||||
|
west += Vector2.LEFT
|
||||||
|
while east.x < project.x_max && image.get_pixelv(east).is_equal_approx(color):
|
||||||
|
east += Vector2.RIGHT
|
||||||
|
for px in range(west.x + 1, east.x):
|
||||||
|
var p := Vector2(px, n.y)
|
||||||
|
_set_pixel(image, p.x, p.y, tool_slot.color)
|
||||||
|
processed.set_bit(p, true)
|
||||||
|
var north := p + Vector2.UP
|
||||||
|
var south := p + Vector2.DOWN
|
||||||
|
if north.y >= project.y_min && image.get_pixelv(north).is_equal_approx(color):
|
||||||
|
q.append(north)
|
||||||
|
if south.y < project.y_max && image.get_pixelv(south).is_equal_approx(color):
|
||||||
|
q.append(south)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_pixel(image : Image, x : int, y : int, color : Color) -> void:
|
||||||
|
if _fill_with == 0 or _pattern == null:
|
||||||
|
image.set_pixel(x, y, color)
|
||||||
|
else:
|
||||||
|
_pattern.image.lock()
|
||||||
|
var size := _pattern.image.get_size()
|
||||||
|
var px := int(x + _offset_x) % int(size.x)
|
||||||
|
var py := int(y + _offset_y) % int(size.y)
|
||||||
|
var pc := _pattern.image.get_pixel(px, py)
|
||||||
|
image.set_pixel(x, y, pc)
|
||||||
|
|
||||||
|
|
||||||
|
func commit_undo(action : String, undo_data : Dictionary) -> void:
|
||||||
|
var redo_data = _get_undo_data()
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
|
||||||
|
|
||||||
|
project.undos += 1
|
||||||
|
project.undo_redo.create_action(action)
|
||||||
|
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() -> Dictionary:
|
||||||
|
var data = {}
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
|
||||||
|
image.unlock()
|
||||||
|
data["image_data"] = image.data
|
||||||
|
image.lock()
|
||||||
|
return data
|
144
src/Tools/Bucket.tscn
Normal file
144
src/Tools/Bucket.tscn
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
[gd_scene load_steps=4 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://assets/graphics/brush_button.png" type="Texture" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=2]
|
||||||
|
[ext_resource path="res://src/Tools/Bucket.gd" type="Script" id=3]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 2 )]
|
||||||
|
script = ExtResource( 3 )
|
||||||
|
|
||||||
|
[node name="Label" parent="." index="0"]
|
||||||
|
margin_right = 131.0
|
||||||
|
|
||||||
|
[node name="FillArea" type="Label" parent="." index="1"]
|
||||||
|
margin_left = 38.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 92.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Fill area:"
|
||||||
|
|
||||||
|
[node name="FillAreaOptions" type="OptionButton" parent="." index="2"]
|
||||||
|
margin_top = 36.0
|
||||||
|
margin_right = 131.0
|
||||||
|
margin_bottom = 56.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Same color area"
|
||||||
|
items = [ "Same color area", null, false, 0, null, "Same color pixels", null, false, 1, null ]
|
||||||
|
selected = 0
|
||||||
|
|
||||||
|
[node name="FillWith" type="Label" parent="." index="3"]
|
||||||
|
margin_left = 38.0
|
||||||
|
margin_top = 60.0
|
||||||
|
margin_right = 92.0
|
||||||
|
margin_bottom = 74.0
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Fill with:"
|
||||||
|
|
||||||
|
[node name="FillWithOptions" type="OptionButton" parent="." index="4"]
|
||||||
|
margin_left = 5.0
|
||||||
|
margin_top = 78.0
|
||||||
|
margin_right = 126.0
|
||||||
|
margin_bottom = 98.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Selected Color"
|
||||||
|
items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ]
|
||||||
|
selected = 0
|
||||||
|
|
||||||
|
[node name="FillPattern" type="VBoxContainer" parent="." index="5"]
|
||||||
|
visible = false
|
||||||
|
margin_left = 22.0
|
||||||
|
margin_top = 102.0
|
||||||
|
margin_right = 108.0
|
||||||
|
margin_bottom = 208.0
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
|
||||||
|
[node name="Type" type="TextureButton" parent="FillPattern" index="0"]
|
||||||
|
margin_left = 27.0
|
||||||
|
margin_right = 59.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
hint_tooltip = "Select a brush"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
texture_normal = ExtResource( 1 )
|
||||||
|
|
||||||
|
[node name="Texture" type="TextureRect" parent="FillPattern/Type" index="0"]
|
||||||
|
margin_right = 32.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
expand = true
|
||||||
|
stretch_mode = 6
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Offset" type="Label" parent="FillPattern" index="1"]
|
||||||
|
margin_top = 36.0
|
||||||
|
margin_right = 86.0
|
||||||
|
margin_bottom = 50.0
|
||||||
|
text = "Offset"
|
||||||
|
align = 1
|
||||||
|
|
||||||
|
[node name="XOffset" type="HBoxContainer" parent="FillPattern" index="2"]
|
||||||
|
margin_top = 54.0
|
||||||
|
margin_right = 86.0
|
||||||
|
margin_bottom = 78.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="FillPattern/XOffset" index="0"]
|
||||||
|
margin_top = 5.0
|
||||||
|
margin_right = 8.0
|
||||||
|
margin_bottom = 19.0
|
||||||
|
text = "X"
|
||||||
|
|
||||||
|
[node name="OffsetX" type="SpinBox" parent="FillPattern/XOffset" index="1"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_right = 86.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
|
||||||
|
[node name="YOffset" type="HBoxContainer" parent="FillPattern" index="3"]
|
||||||
|
margin_top = 82.0
|
||||||
|
margin_right = 86.0
|
||||||
|
margin_bottom = 106.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="FillPattern/YOffset" index="0"]
|
||||||
|
margin_top = 5.0
|
||||||
|
margin_right = 7.0
|
||||||
|
margin_bottom = 19.0
|
||||||
|
text = "Y"
|
||||||
|
|
||||||
|
[node name="OffsetY" type="SpinBox" parent="FillPattern/YOffset" index="1"]
|
||||||
|
margin_left = 11.0
|
||||||
|
margin_right = 85.0
|
||||||
|
margin_bottom = 24.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
|
||||||
|
[node name="PixelPerfect" parent="." index="6"]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="EmptySpacer" parent="." index="7"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 212.0
|
||||||
|
margin_right = 131.0
|
||||||
|
margin_bottom = 224.0
|
||||||
|
|
||||||
|
[node name="Mirror" parent="." index="8"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 212.0
|
||||||
|
margin_right = 131.0
|
||||||
|
margin_bottom = 229.0
|
||||||
|
|
||||||
|
[node name="Horizontal" parent="Mirror" index="0"]
|
||||||
|
margin_left = 27.0
|
||||||
|
margin_right = 42.0
|
||||||
|
|
||||||
|
[node name="Vertical" parent="Mirror" index="1"]
|
||||||
|
margin_left = 86.0
|
||||||
|
margin_right = 103.0
|
||||||
|
[connection signal="item_selected" from="FillAreaOptions" to="." method="_on_FillAreaOptions_item_selected"]
|
||||||
|
[connection signal="item_selected" from="FillWithOptions" to="." method="_on_FillWithOptions_item_selected"]
|
||||||
|
[connection signal="pressed" from="FillPattern/Type" to="." method="_on_PatternType_pressed"]
|
||||||
|
[connection signal="value_changed" from="FillPattern/XOffset/OffsetX" to="." method="_on_PatternOffsetX_value_changed"]
|
||||||
|
[connection signal="value_changed" from="FillPattern/YOffset/OffsetY" to="." method="_on_PatternOffsetY_value_changed"]
|
45
src/Tools/ColorPicker.gd
Normal file
45
src/Tools/ColorPicker.gd
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
extends "res://src/Tools/Base.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _color_slot := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Options_item_selected(id):
|
||||||
|
_color_slot = id
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func get_config() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"color_slot" : _color_slot,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func set_config(config : Dictionary) -> void:
|
||||||
|
_color_slot = config.get("color_slot", _color_slot)
|
||||||
|
|
||||||
|
|
||||||
|
func update_config() -> void:
|
||||||
|
$ColorPicker/Options.selected = _color_slot
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(position : Vector2) -> void:
|
||||||
|
_pick_color(position)
|
||||||
|
|
||||||
|
|
||||||
|
func draw_move(position : Vector2) -> void:
|
||||||
|
_pick_color(position)
|
||||||
|
|
||||||
|
|
||||||
|
func draw_end(_position : Vector2) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _pick_color(position : Vector2) -> void:
|
||||||
|
var image := Image.new()
|
||||||
|
image.copy_from(_get_draw_image())
|
||||||
|
image.lock()
|
||||||
|
var color := image.get_pixelv(position)
|
||||||
|
var button := BUTTON_LEFT if _color_slot == 0 else BUTTON_RIGHT
|
||||||
|
Tools.assign_color(color, button)
|
47
src/Tools/ColorPicker.tscn
Normal file
47
src/Tools/ColorPicker.tscn
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/ColorPicker.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 1 )]
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="ColorPicker" type="VBoxContainer" parent="." index="1"]
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 56.0
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="ColorPicker" index="0"]
|
||||||
|
margin_left = 32.0
|
||||||
|
margin_right = 83.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Pick for:"
|
||||||
|
|
||||||
|
[node name="Options" type="OptionButton" parent="ColorPicker" index="1"]
|
||||||
|
margin_left = 13.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 103.0
|
||||||
|
margin_bottom = 38.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Left Color"
|
||||||
|
items = [ "Left Color", null, false, 0, null, "Right Color", null, false, 1, null ]
|
||||||
|
selected = 0
|
||||||
|
|
||||||
|
[node name="PixelPerfect" parent="." index="2"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 60.0
|
||||||
|
margin_bottom = 84.0
|
||||||
|
|
||||||
|
[node name="EmptySpacer" parent="." index="3"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 60.0
|
||||||
|
margin_bottom = 72.0
|
||||||
|
|
||||||
|
[node name="Mirror" parent="." index="4"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 60.0
|
||||||
|
margin_bottom = 77.0
|
||||||
|
[connection signal="item_selected" from="ColorPicker/Options" to="." method="_on_Options_item_selected"]
|
494
src/Tools/Draw.gd
Normal file
494
src/Tools/Draw.gd
Normal file
|
@ -0,0 +1,494 @@
|
||||||
|
extends "res://src/Tools/Base.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _brush := Brushes.get_default_brush()
|
||||||
|
var _brush_size := 1
|
||||||
|
var _brush_interpolate := 0
|
||||||
|
var _brush_image := Image.new()
|
||||||
|
var _brush_texture := ImageTexture.new()
|
||||||
|
var _strength := 1.0
|
||||||
|
|
||||||
|
var _undo_data := {}
|
||||||
|
var _drawer := Drawer.new()
|
||||||
|
var _mask := PoolByteArray()
|
||||||
|
var _mirror_brushes := {}
|
||||||
|
|
||||||
|
var _draw_line := false
|
||||||
|
var _line_start := Vector2.ZERO
|
||||||
|
var _line_end := Vector2.ZERO
|
||||||
|
|
||||||
|
var _indicator := BitMap.new()
|
||||||
|
var _polylines := []
|
||||||
|
var _line_polylines := []
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
Tools.connect("color_changed", self, "_on_Color_changed")
|
||||||
|
Global.brushes_popup.connect("brush_removed", self, "_on_Brush_removed")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_BrushType_pressed() -> void:
|
||||||
|
if not Global.brushes_popup.is_connected("brush_selected", self, "_on_Brush_selected"):
|
||||||
|
Global.brushes_popup.connect("brush_selected", self, "_on_Brush_selected", [], CONNECT_ONESHOT)
|
||||||
|
Global.brushes_popup.popup(Rect2($Brush/Type.rect_global_position, Vector2(226, 72)))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Brush_selected(brush : Brushes.Brush) -> void:
|
||||||
|
_brush = brush
|
||||||
|
update_brush()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_BrushSize_value_changed(value : float) -> void:
|
||||||
|
_brush_size = int(value)
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_InterpolateFactor_value_changed(value : float) -> void:
|
||||||
|
_brush_interpolate = int(value)
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Color_changed(_color : Color, _button : int) -> void:
|
||||||
|
update_brush()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_Brush_removed(brush : Brushes.Brush) -> void:
|
||||||
|
if brush == _brush:
|
||||||
|
_brush = Brushes.get_default_brush()
|
||||||
|
update_brush()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func get_config() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"brush_type" : _brush.type,
|
||||||
|
"brush_index" : _brush.index,
|
||||||
|
"brush_size" : _brush_size,
|
||||||
|
"brush_interpolate" : _brush_interpolate,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func set_config(config : Dictionary) -> void:
|
||||||
|
var type = config.get("brush_type", _brush.type)
|
||||||
|
var index = config.get("brush_index", _brush.index)
|
||||||
|
_brush = Global.brushes_popup.get_brush(type, index)
|
||||||
|
_brush_size = config.get("brush_size", _brush_size)
|
||||||
|
_brush_interpolate = config.get("brush_interpolate", _brush_interpolate)
|
||||||
|
|
||||||
|
|
||||||
|
func update_config() -> void:
|
||||||
|
$Brush/Size.value = _brush_size
|
||||||
|
$BrushSize.value = _brush_size
|
||||||
|
$ColorInterpolation/Factor.value = _brush_interpolate
|
||||||
|
$ColorInterpolation/Slider.value = _brush_interpolate
|
||||||
|
update_brush()
|
||||||
|
|
||||||
|
|
||||||
|
func update_brush() -> void:
|
||||||
|
match _brush.type:
|
||||||
|
Brushes.PIXEL:
|
||||||
|
_brush_texture.create_from_image(load("res://assets/graphics/pixel_image.png"), 0)
|
||||||
|
Brushes.CIRCLE:
|
||||||
|
_brush_texture.create_from_image(load("res://assets/graphics/circle_9x9.png"), 0)
|
||||||
|
Brushes.FILLED_CIRCLE:
|
||||||
|
_brush_texture.create_from_image(load("res://assets/graphics/circle_filled_9x9.png"), 0)
|
||||||
|
Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM:
|
||||||
|
if _brush.random.size() <= 1:
|
||||||
|
_brush_image = _create_blended_brush_image(_brush.image)
|
||||||
|
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()
|
||||||
|
_polylines = _create_polylines(_indicator)
|
||||||
|
$Brush/Type/Texture.texture = _brush_texture
|
||||||
|
$ColorInterpolation.visible = _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM]
|
||||||
|
|
||||||
|
|
||||||
|
func update_random_image() -> void:
|
||||||
|
if _brush.type != Brushes.RANDOM_FILE:
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
func update_mirror_brush() -> void:
|
||||||
|
_mirror_brushes.x = _brush_image.duplicate()
|
||||||
|
_mirror_brushes.x.flip_x()
|
||||||
|
_mirror_brushes.y = _brush_image.duplicate()
|
||||||
|
_mirror_brushes.y.flip_y()
|
||||||
|
_mirror_brushes.xy = _mirror_brushes.x.duplicate()
|
||||||
|
_mirror_brushes.xy.flip_y()
|
||||||
|
|
||||||
|
|
||||||
|
func update_mask() -> void:
|
||||||
|
var size := _get_draw_image().get_size()
|
||||||
|
_mask = PoolByteArray()
|
||||||
|
_mask.resize(size.x * size.y)
|
||||||
|
for i in _mask.size():
|
||||||
|
_mask[i] = 0
|
||||||
|
|
||||||
|
|
||||||
|
func update_line_polylines(start : Vector2, end : Vector2) -> void:
|
||||||
|
var indicator := _create_line_indicator(_indicator, start, end)
|
||||||
|
_line_polylines = _create_polylines(indicator)
|
||||||
|
|
||||||
|
|
||||||
|
func restore_image() -> void:
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var image = project.frames[project.current_frame].cels[project.current_layer].image
|
||||||
|
image.unlock()
|
||||||
|
image.data = _undo_data[image]
|
||||||
|
image.lock()
|
||||||
|
|
||||||
|
|
||||||
|
func prepare_undo() -> void:
|
||||||
|
_undo_data = _get_undo_data()
|
||||||
|
|
||||||
|
|
||||||
|
func commit_undo(action : String) -> void:
|
||||||
|
var redo_data = _get_undo_data()
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var frame := -1
|
||||||
|
var layer := -1
|
||||||
|
if Global.animation_timer.is_stopped():
|
||||||
|
frame = project.current_frame
|
||||||
|
layer = project.current_layer
|
||||||
|
|
||||||
|
project.undos += 1
|
||||||
|
project.undo_redo.create_action(action)
|
||||||
|
for image in redo_data:
|
||||||
|
project.undo_redo.add_do_property(image, "data", redo_data[image])
|
||||||
|
for image in _undo_data:
|
||||||
|
project.undo_redo.add_undo_property(image, "data", _undo_data[image])
|
||||||
|
project.undo_redo.add_do_method(Global, "redo", frame, layer)
|
||||||
|
project.undo_redo.add_undo_method(Global, "undo", frame, layer)
|
||||||
|
project.undo_redo.commit_action()
|
||||||
|
|
||||||
|
_undo_data.clear()
|
||||||
|
|
||||||
|
|
||||||
|
func draw_tool(position : Vector2) -> void:
|
||||||
|
var strength := _strength
|
||||||
|
if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.ALPHA:
|
||||||
|
strength *= Tools.pen_pressure
|
||||||
|
|
||||||
|
_drawer.pixel_perfect = tool_slot.pixel_perfect if _brush_size == 1 else false
|
||||||
|
_drawer.horizontal_mirror = tool_slot.horizontal_mirror
|
||||||
|
_drawer.vertical_mirror = tool_slot.vertical_mirror
|
||||||
|
_drawer.color_op.strength = strength
|
||||||
|
|
||||||
|
match _brush.type:
|
||||||
|
Brushes.PIXEL:
|
||||||
|
draw_tool_pixel(position)
|
||||||
|
Brushes.CIRCLE:
|
||||||
|
draw_tool_circle(position, false)
|
||||||
|
Brushes.FILLED_CIRCLE:
|
||||||
|
draw_tool_circle(position, true)
|
||||||
|
_:
|
||||||
|
draw_tool_brush(position)
|
||||||
|
|
||||||
|
|
||||||
|
# Bresenham's Algorithm
|
||||||
|
# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
|
||||||
|
func draw_fill_gap(start : Vector2, end : Vector2) -> void:
|
||||||
|
var dx := int(abs(end.x - start.x))
|
||||||
|
var dy := int(-abs(end.y - start.y))
|
||||||
|
var err := dx + dy
|
||||||
|
var e2 := err << 1
|
||||||
|
var sx = 1 if start.x < end.x else -1
|
||||||
|
var sy = 1 if start.y < end.y else -1
|
||||||
|
var x = start.x
|
||||||
|
var y = start.y
|
||||||
|
while !(x == end.x && y == end.y):
|
||||||
|
e2 = err << 1
|
||||||
|
if e2 >= dy:
|
||||||
|
err += dy
|
||||||
|
x += sx
|
||||||
|
if e2 <= dx:
|
||||||
|
err += dx
|
||||||
|
y += sy
|
||||||
|
draw_tool(Vector2(x, y))
|
||||||
|
|
||||||
|
|
||||||
|
func draw_tool_pixel(position : Vector2) -> void:
|
||||||
|
var start := position - Vector2.ONE * (_brush_size >> 1)
|
||||||
|
var end := start + Vector2.ONE * _brush_size
|
||||||
|
for y in range(start.y, end.y):
|
||||||
|
for x in range(start.x, end.x):
|
||||||
|
_set_pixel(Vector2(x, y))
|
||||||
|
|
||||||
|
|
||||||
|
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
|
||||||
|
func draw_tool_circle(position : Vector2, fill := false) -> void:
|
||||||
|
var r := _brush_size
|
||||||
|
var x := -r
|
||||||
|
var y := 0
|
||||||
|
var err := 2 - r * 2
|
||||||
|
var draw := true
|
||||||
|
if fill:
|
||||||
|
_set_pixel(position)
|
||||||
|
while x < 0:
|
||||||
|
if draw:
|
||||||
|
for i in range(1 if fill else -x, -x + 1):
|
||||||
|
_set_pixel(position + Vector2(-i, y))
|
||||||
|
_set_pixel(position + Vector2(-y, -i))
|
||||||
|
_set_pixel(position + Vector2(i, -y))
|
||||||
|
_set_pixel(position + Vector2(y, i))
|
||||||
|
draw = not fill
|
||||||
|
r = err
|
||||||
|
if r <= y:
|
||||||
|
y += 1
|
||||||
|
err += y * 2 + 1
|
||||||
|
draw = true
|
||||||
|
if r > x || err > y:
|
||||||
|
x += 1
|
||||||
|
err += x * 2 + 1
|
||||||
|
|
||||||
|
|
||||||
|
func draw_tool_brush(position : Vector2) -> void:
|
||||||
|
if Global.tile_mode and _get_tile_mode_rect().has_point(position):
|
||||||
|
position = position.posmodv(Global.current_project.size)
|
||||||
|
|
||||||
|
var size := _brush_image.get_size()
|
||||||
|
var dst := position - (size / 2).floor()
|
||||||
|
var dst_rect := Rect2(dst, size)
|
||||||
|
var draw_rect := _get_draw_rect()
|
||||||
|
dst_rect = dst_rect.clip(draw_rect)
|
||||||
|
if dst_rect.size == Vector2.ZERO:
|
||||||
|
return
|
||||||
|
var src_rect := Rect2(dst_rect.position - dst, dst_rect.size)
|
||||||
|
dst = dst_rect.position
|
||||||
|
|
||||||
|
var mirror_x = draw_rect.end.x + draw_rect.position.x - dst.x - src_rect.size.x
|
||||||
|
var mirror_y = draw_rect.end.y + draw_rect.position.y - dst.y - src_rect.size.y
|
||||||
|
|
||||||
|
_draw_brush_image(_brush_image, src_rect, dst)
|
||||||
|
if tool_slot.horizontal_mirror:
|
||||||
|
_draw_brush_image(_mirror_brushes.x, _flip_rect(src_rect, size, true, false), Vector2(mirror_x, dst.y))
|
||||||
|
if tool_slot.vertical_mirror:
|
||||||
|
_draw_brush_image(_mirror_brushes.xy, _flip_rect(src_rect, size, true, true), Vector2(mirror_x, mirror_y))
|
||||||
|
if tool_slot.vertical_mirror:
|
||||||
|
_draw_brush_image(_mirror_brushes.y, _flip_rect(src_rect, size, false, true), Vector2(dst.x, mirror_y))
|
||||||
|
|
||||||
|
|
||||||
|
func draw_indicator() -> void:
|
||||||
|
draw_indicator_at(_cursor, Vector2.ZERO, Color.blue)
|
||||||
|
if Global.tile_mode and _get_tile_mode_rect().has_point(_cursor):
|
||||||
|
var tile := _line_start if _draw_line else _cursor
|
||||||
|
if not _get_draw_rect().has_point(tile):
|
||||||
|
var offset := tile - tile.posmodv(Global.current_project.size)
|
||||||
|
draw_indicator_at(_cursor, offset, Color.green)
|
||||||
|
|
||||||
|
|
||||||
|
func draw_indicator_at(position : Vector2, offset : Vector2, color : Color) -> void:
|
||||||
|
var canvas = Global.canvas
|
||||||
|
if _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM] and not _draw_line:
|
||||||
|
position -= (_brush_image.get_size() / 2).floor()
|
||||||
|
position -= offset
|
||||||
|
canvas.draw_texture(_brush_texture, position)
|
||||||
|
else:
|
||||||
|
if _draw_line:
|
||||||
|
position.x = _line_end.x if _line_end.x < _line_start.x else _line_start.x
|
||||||
|
position.y = _line_end.y if _line_end.y < _line_start.y else _line_start.y
|
||||||
|
position -= (_indicator.get_size() / 2).floor()
|
||||||
|
position -= offset
|
||||||
|
canvas.draw_set_transform(position, canvas.rotation, canvas.scale)
|
||||||
|
var polylines := _line_polylines if _draw_line else _polylines
|
||||||
|
for line in polylines:
|
||||||
|
var pool := PoolVector2Array(line)
|
||||||
|
canvas.draw_polyline(pool, color)
|
||||||
|
canvas.draw_set_transform(canvas.position, canvas.rotation, canvas.scale)
|
||||||
|
|
||||||
|
|
||||||
|
func _set_pixel(position : Vector2) -> void:
|
||||||
|
if Global.tile_mode and _get_tile_mode_rect().has_point(position):
|
||||||
|
position = position.posmodv(Global.current_project.size)
|
||||||
|
|
||||||
|
if not _get_draw_rect().has_point(position):
|
||||||
|
return
|
||||||
|
|
||||||
|
var image := _get_draw_image()
|
||||||
|
var i := int(position.x + position.y * image.get_size().x)
|
||||||
|
if _mask[i] < Tools.pen_pressure:
|
||||||
|
_mask[i] = Tools.pen_pressure
|
||||||
|
_drawer.set_pixel(image, position, tool_slot.color)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_brush_image(_image : Image, _src_rect: Rect2, _dst: Vector2) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func _create_blended_brush_image(image : Image) -> Image:
|
||||||
|
var size := image.get_size() * _brush_size
|
||||||
|
var brush := Image.new()
|
||||||
|
brush.copy_from(image)
|
||||||
|
brush = _blend_image(brush, tool_slot.color, _brush_interpolate / 100.0)
|
||||||
|
brush.unlock()
|
||||||
|
brush.resize(size.x, size.y, Image.INTERPOLATE_NEAREST)
|
||||||
|
return brush
|
||||||
|
|
||||||
|
|
||||||
|
func _blend_image(image : Image, color : Color, factor : float) -> Image:
|
||||||
|
var size := image.get_size()
|
||||||
|
image.lock()
|
||||||
|
for y in size.y:
|
||||||
|
for x in size.x:
|
||||||
|
var color_old := image.get_pixel(x, y)
|
||||||
|
if color_old.a > 0:
|
||||||
|
var color_new := color_old.linear_interpolate(color, factor)
|
||||||
|
color_new.a = color_old.a
|
||||||
|
image.set_pixel(x, y, color_new)
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
func _create_brush_indicator() -> BitMap:
|
||||||
|
match _brush.type:
|
||||||
|
Brushes.PIXEL:
|
||||||
|
return _create_pixel_indicator(_brush_size)
|
||||||
|
Brushes.CIRCLE:
|
||||||
|
return _create_circle_indicator(_brush_size, false)
|
||||||
|
Brushes.FILLED_CIRCLE:
|
||||||
|
return _create_circle_indicator(_brush_size, true)
|
||||||
|
_:
|
||||||
|
return _create_image_indicator(_brush_image)
|
||||||
|
|
||||||
|
|
||||||
|
func _create_image_indicator(image : Image) -> BitMap:
|
||||||
|
var bitmap := BitMap.new()
|
||||||
|
bitmap.create_from_image_alpha(image, 0.0)
|
||||||
|
return bitmap
|
||||||
|
|
||||||
|
|
||||||
|
func _create_pixel_indicator(size : int) -> BitMap:
|
||||||
|
var bitmap := BitMap.new()
|
||||||
|
bitmap.create(Vector2.ONE * size)
|
||||||
|
bitmap.set_bit_rect(Rect2(Vector2.ZERO, Vector2.ONE * size), true)
|
||||||
|
return bitmap
|
||||||
|
|
||||||
|
|
||||||
|
func _create_circle_indicator(size : int, fill := false) -> BitMap:
|
||||||
|
var bitmap := BitMap.new()
|
||||||
|
bitmap.create(Vector2.ONE * (size * 2 + 1))
|
||||||
|
var position := Vector2(size, size)
|
||||||
|
|
||||||
|
var r := size
|
||||||
|
var x := -r
|
||||||
|
var y := 0
|
||||||
|
var err := 2 - r * 2
|
||||||
|
var draw := true
|
||||||
|
if fill:
|
||||||
|
bitmap.set_bit(position, true)
|
||||||
|
while x < 0:
|
||||||
|
if draw:
|
||||||
|
for i in range(1 if fill else -x, -x + 1):
|
||||||
|
bitmap.set_bit(position + Vector2(-i, y), true)
|
||||||
|
bitmap.set_bit(position + Vector2(-y, -i), true)
|
||||||
|
bitmap.set_bit(position + Vector2(i, -y), true)
|
||||||
|
bitmap.set_bit(position + Vector2(y, i), true)
|
||||||
|
draw = not fill
|
||||||
|
r = err
|
||||||
|
if r <= y:
|
||||||
|
y += 1
|
||||||
|
err += y * 2 + 1
|
||||||
|
draw = true
|
||||||
|
if r > x || err > y:
|
||||||
|
x += 1
|
||||||
|
err += x * 2 + 1
|
||||||
|
return bitmap
|
||||||
|
|
||||||
|
|
||||||
|
func _create_line_indicator(indicator : BitMap, start : Vector2, end : Vector2) -> BitMap:
|
||||||
|
var bitmap := BitMap.new()
|
||||||
|
var size := (end - start).abs() + indicator.get_size()
|
||||||
|
bitmap.create(size)
|
||||||
|
|
||||||
|
var offset := (indicator.get_size() / 2).floor()
|
||||||
|
var diff := end - start
|
||||||
|
start.x = -diff.x if diff.x < 0 else 0.0
|
||||||
|
end.x = 0.0 if diff.x < 0 else diff.x
|
||||||
|
start.y = -diff.y if diff.y < 0 else 0.0
|
||||||
|
end.y = 0.0 if diff.y < 0 else diff.y
|
||||||
|
start += offset
|
||||||
|
end += offset
|
||||||
|
|
||||||
|
var dx := int(abs(end.x - start.x))
|
||||||
|
var dy := int(-abs(end.y - start.y))
|
||||||
|
var err := dx + dy
|
||||||
|
var e2 := err << 1
|
||||||
|
var sx = 1 if start.x < end.x else -1
|
||||||
|
var sy = 1 if start.y < end.y else -1
|
||||||
|
var x = start.x
|
||||||
|
var y = start.y
|
||||||
|
while !(x == end.x && y == end.y):
|
||||||
|
_blit_indicator(bitmap, indicator, Vector2(x, y))
|
||||||
|
e2 = err << 1
|
||||||
|
if e2 >= dy:
|
||||||
|
err += dy
|
||||||
|
x += sx
|
||||||
|
if e2 <= dx:
|
||||||
|
err += dx
|
||||||
|
y += sy
|
||||||
|
_blit_indicator(bitmap, indicator, Vector2(x, y))
|
||||||
|
return bitmap
|
||||||
|
|
||||||
|
|
||||||
|
func _blit_indicator(dst : BitMap, indicator : BitMap, position : Vector2) -> void:
|
||||||
|
var rect := Rect2(Vector2.ZERO, dst.get_size())
|
||||||
|
var size := indicator.get_size()
|
||||||
|
position -= (size / 2).floor()
|
||||||
|
for y in size.y:
|
||||||
|
for x in size.x:
|
||||||
|
var pos := Vector2(x, y)
|
||||||
|
var bit := indicator.get_bit(pos)
|
||||||
|
pos += position
|
||||||
|
if bit and rect.has_point(pos):
|
||||||
|
dst.set_bit(pos, bit)
|
||||||
|
|
||||||
|
|
||||||
|
func _line_angle_constraint(start : Vector2, end : Vector2) -> Dictionary:
|
||||||
|
var result := {}
|
||||||
|
var angle := rad2deg(end.angle_to_point(start))
|
||||||
|
var distance := start.distance_to(end)
|
||||||
|
if Tools.control:
|
||||||
|
if tool_slot.pixel_perfect:
|
||||||
|
angle = stepify(angle, 22.5)
|
||||||
|
if step_decimals(angle) != 0:
|
||||||
|
var diff := end - start
|
||||||
|
var v := Vector2(2 , 1) if abs(diff.x) > abs(diff.y) else Vector2(1 , 2)
|
||||||
|
var p := diff.project(diff.sign() * v).abs().round()
|
||||||
|
var f := p.y if abs(diff.x) > abs(diff.y) else p.x
|
||||||
|
end = start + diff.sign() * v * f - diff.sign()
|
||||||
|
angle = rad2deg(atan2(sign(diff.y) * v.y, sign(diff.x) * v.x))
|
||||||
|
else:
|
||||||
|
end = start + Vector2.RIGHT.rotated(deg2rad(angle)) * distance
|
||||||
|
else:
|
||||||
|
angle = stepify(angle, 15)
|
||||||
|
end = start + Vector2.RIGHT.rotated(deg2rad(angle)) * distance
|
||||||
|
angle *= -1
|
||||||
|
angle += 360 if angle < 0 else 0
|
||||||
|
result.text = str(stepify(angle, 0.01)) + "°"
|
||||||
|
result.position = end.round()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _get_undo_data() -> Dictionary:
|
||||||
|
var data = {}
|
||||||
|
var project : Project = Global.current_project
|
||||||
|
var frames := project.frames
|
||||||
|
if Global.animation_timer.is_stopped():
|
||||||
|
frames = [project.frames[project.current_frame]]
|
||||||
|
for frame in frames:
|
||||||
|
var image : Image = frame.cels[project.current_layer].image
|
||||||
|
image.unlock()
|
||||||
|
data[image] = image.data
|
||||||
|
image.lock()
|
||||||
|
return data
|
114
src/Tools/Draw.tscn
Normal file
114
src/Tools/Draw.tscn
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
[gd_scene load_steps=4 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://assets/graphics/brush_button.png" type="Texture" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=2]
|
||||||
|
[ext_resource path="res://src/Tools/Draw.gd" type="Script" id=3]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 2 )]
|
||||||
|
script = ExtResource( 3 )
|
||||||
|
|
||||||
|
[node name="Brush" type="HBoxContainer" parent="." index="1"]
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 50.0
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Type" type="TextureButton" parent="Brush" index="0"]
|
||||||
|
margin_left = 1.0
|
||||||
|
margin_right = 37.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
rect_min_size = Vector2( 36, 32 )
|
||||||
|
hint_tooltip = "Select a brush"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 0
|
||||||
|
texture_normal = ExtResource( 1 )
|
||||||
|
|
||||||
|
[node name="Texture" type="TextureRect" parent="Brush/Type" index="0"]
|
||||||
|
margin_right = 32.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
expand = true
|
||||||
|
stretch_mode = 6
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="Size" type="SpinBox" parent="Brush" index="1"]
|
||||||
|
margin_left = 41.0
|
||||||
|
margin_right = 115.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
min_value = 1.0
|
||||||
|
value = 1.0
|
||||||
|
align = 1
|
||||||
|
suffix = "px"
|
||||||
|
|
||||||
|
[node name="BrushSize" type="HSlider" parent="." index="2"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 54.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 70.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
focus_mode = 0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 1
|
||||||
|
min_value = 1.0
|
||||||
|
value = 1.0
|
||||||
|
allow_greater = true
|
||||||
|
ticks_on_borders = true
|
||||||
|
|
||||||
|
[node name="PixelPerfect" parent="." index="3"]
|
||||||
|
margin_top = 74.0
|
||||||
|
margin_bottom = 98.0
|
||||||
|
|
||||||
|
[node name="ColorInterpolation" type="VBoxContainer" parent="." index="4"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 102.0
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 164.0
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="ColorInterpolation" index="0"]
|
||||||
|
margin_left = 4.0
|
||||||
|
margin_right = 111.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
hint_tooltip = "0: Color from the brush itself, 100: the currently selected color"
|
||||||
|
mouse_filter = 1
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Brush color from"
|
||||||
|
|
||||||
|
[node name="Factor" type="SpinBox" parent="ColorInterpolation" index="1"]
|
||||||
|
margin_left = 21.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 95.0
|
||||||
|
margin_bottom = 42.0
|
||||||
|
hint_tooltip = "0: Color from the brush itself, 100: the currently selected color"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
align = 1
|
||||||
|
|
||||||
|
[node name="Slider" type="HSlider" parent="ColorInterpolation" index="2"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 46.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 62.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
hint_tooltip = "0: Color from the brush itself, 100: the currently selected color"
|
||||||
|
focus_mode = 0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 1
|
||||||
|
ticks_on_borders = true
|
||||||
|
|
||||||
|
[node name="EmptySpacer" parent="." index="5"]
|
||||||
|
margin_top = 102.0
|
||||||
|
margin_bottom = 114.0
|
||||||
|
|
||||||
|
[node name="Mirror" parent="." index="6"]
|
||||||
|
margin_top = 118.0
|
||||||
|
margin_bottom = 135.0
|
||||||
|
[connection signal="pressed" from="Brush/Type" to="." method="_on_BrushType_pressed"]
|
||||||
|
[connection signal="value_changed" from="Brush/Size" to="." method="_on_BrushSize_value_changed"]
|
||||||
|
[connection signal="value_changed" from="BrushSize" to="." method="_on_BrushSize_value_changed"]
|
||||||
|
[connection signal="value_changed" from="ColorInterpolation/Factor" to="." method="_on_InterpolateFactor_value_changed"]
|
||||||
|
[connection signal="value_changed" from="ColorInterpolation/Slider" to="." method="_on_InterpolateFactor_value_changed"]
|
75
src/Tools/Eraser.gd
Normal file
75
src/Tools/Eraser.gd
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
extends "res://src/Tools/Draw.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _last_position := Vector2.INF
|
||||||
|
var _clear_image := Image.new()
|
||||||
|
var _changed := false
|
||||||
|
|
||||||
|
|
||||||
|
class EraseOp extends Drawer.ColorOp:
|
||||||
|
var changed := false
|
||||||
|
|
||||||
|
|
||||||
|
func process(_src: Color, _dst: Color) -> Color:
|
||||||
|
changed = true
|
||||||
|
# dst.a -= src.a * strength
|
||||||
|
# return dst
|
||||||
|
return Color(0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_drawer.color_op = EraseOp.new()
|
||||||
|
_clear_image.create(1, 1, false, Image.FORMAT_RGBA8)
|
||||||
|
_clear_image.fill(Color(0, 0, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(position : Vector2) -> void:
|
||||||
|
update_mask()
|
||||||
|
_changed = false
|
||||||
|
_drawer.color_op.changed = false
|
||||||
|
|
||||||
|
prepare_undo()
|
||||||
|
_drawer.reset()
|
||||||
|
|
||||||
|
_draw_line = Tools.shift
|
||||||
|
if _draw_line:
|
||||||
|
_line_start = position
|
||||||
|
_line_end = position
|
||||||
|
update_line_polylines(_line_start, _line_end)
|
||||||
|
else:
|
||||||
|
draw_tool(position)
|
||||||
|
_last_position = position
|
||||||
|
Global.canvas.sprite_changed_this_frame = true
|
||||||
|
cursor_text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func draw_move(position : Vector2) -> void:
|
||||||
|
if _draw_line:
|
||||||
|
var d = _line_angle_constraint(_line_start, position)
|
||||||
|
_line_end = d.position
|
||||||
|
cursor_text = d.text
|
||||||
|
update_line_polylines(_line_start, _line_end)
|
||||||
|
else:
|
||||||
|
draw_fill_gap(_last_position, position)
|
||||||
|
_last_position = position
|
||||||
|
cursor_text = ""
|
||||||
|
Global.canvas.sprite_changed_this_frame = true
|
||||||
|
|
||||||
|
|
||||||
|
func draw_end(_position : Vector2) -> void:
|
||||||
|
if _draw_line:
|
||||||
|
draw_tool(_line_start)
|
||||||
|
draw_fill_gap(_line_start, _line_end)
|
||||||
|
_draw_line = false
|
||||||
|
if _changed or _drawer.color_op.changed:
|
||||||
|
commit_undo("Draw")
|
||||||
|
cursor_text = ""
|
||||||
|
update_random_image()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_brush_image(_image : Image, src_rect: Rect2, dst: Vector2) -> void:
|
||||||
|
_changed = true
|
||||||
|
var size := _image.get_size()
|
||||||
|
if _clear_image.get_size() != size:
|
||||||
|
_clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST)
|
||||||
|
_get_draw_image().blit_rect_mask(_clear_image, _image, src_rect, dst)
|
7
src/Tools/Eraser.tscn
Normal file
7
src/Tools/Eraser.tscn
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Draw.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/Eraser.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 1 )]
|
||||||
|
script = ExtResource( 2 )
|
112
src/Tools/LightenDarken.gd
Normal file
112
src/Tools/LightenDarken.gd
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
extends "res://src/Tools/Draw.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _last_position := Vector2.INF
|
||||||
|
var _changed := false
|
||||||
|
var _mode := 0
|
||||||
|
var _amount := 10
|
||||||
|
|
||||||
|
|
||||||
|
class LightenDarkenOp extends Drawer.ColorOp:
|
||||||
|
var changed := false
|
||||||
|
|
||||||
|
|
||||||
|
func process(_src: Color, dst: Color) -> Color:
|
||||||
|
changed = true
|
||||||
|
if strength > 0:
|
||||||
|
return dst.lightened(strength)
|
||||||
|
elif strength < 0:
|
||||||
|
return dst.darkened(-strength)
|
||||||
|
else:
|
||||||
|
return dst
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_drawer.color_op = LightenDarkenOp.new()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_LightenDarken_item_selected(id : int):
|
||||||
|
_mode = id
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_LightenDarken_value_changed(value : float):
|
||||||
|
_amount = int(value)
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func get_config() -> Dictionary:
|
||||||
|
var config := .get_config()
|
||||||
|
config["mode"] = _mode
|
||||||
|
config["amount"] = _amount
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
func set_config(config : Dictionary) -> void:
|
||||||
|
.set_config(config)
|
||||||
|
_mode = config.get("mode", _mode)
|
||||||
|
_amount = config.get("amount", _amount)
|
||||||
|
|
||||||
|
|
||||||
|
func update_config() -> void:
|
||||||
|
.update_config()
|
||||||
|
$LightenDarken.selected = _mode
|
||||||
|
$Amount/Spinbox.value = _amount
|
||||||
|
$Amount/Slider.value = _amount
|
||||||
|
update_strength()
|
||||||
|
|
||||||
|
|
||||||
|
func update_strength() -> void:
|
||||||
|
var factor = 1 if _mode == 0 else -1
|
||||||
|
_strength = _amount * factor / 100.0
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(position : Vector2) -> void:
|
||||||
|
update_mask()
|
||||||
|
_changed = false
|
||||||
|
_drawer.color_op.changed = false
|
||||||
|
|
||||||
|
prepare_undo()
|
||||||
|
_drawer.reset()
|
||||||
|
|
||||||
|
_draw_line = Tools.shift
|
||||||
|
if _draw_line:
|
||||||
|
_line_start = position
|
||||||
|
_line_end = position
|
||||||
|
update_line_polylines(_line_start, _line_end)
|
||||||
|
else:
|
||||||
|
draw_tool(position)
|
||||||
|
_last_position = position
|
||||||
|
Global.canvas.sprite_changed_this_frame = true
|
||||||
|
cursor_text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func draw_move(position : Vector2) -> void:
|
||||||
|
if _draw_line:
|
||||||
|
var d = _line_angle_constraint(_line_start, position)
|
||||||
|
_line_end = d.position
|
||||||
|
cursor_text = d.text
|
||||||
|
update_line_polylines(_line_start, _line_end)
|
||||||
|
else:
|
||||||
|
draw_fill_gap(_last_position, position)
|
||||||
|
_last_position = position
|
||||||
|
cursor_text = ""
|
||||||
|
Global.canvas.sprite_changed_this_frame = true
|
||||||
|
|
||||||
|
|
||||||
|
func draw_end(_position : Vector2) -> void:
|
||||||
|
if _draw_line:
|
||||||
|
draw_tool(_line_start)
|
||||||
|
draw_fill_gap(_line_start, _line_end)
|
||||||
|
_draw_line = false
|
||||||
|
if _changed or _drawer.color_op.changed:
|
||||||
|
commit_undo("Draw")
|
||||||
|
cursor_text = ""
|
||||||
|
update_random_image()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_brush_image(_image : Image, _src_rect: Rect2, _dst: Vector2) -> void:
|
||||||
|
_changed = true
|
||||||
|
draw_tool_pixel(_cursor.floor())
|
68
src/Tools/LightenDarken.tscn
Normal file
68
src/Tools/LightenDarken.tscn
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Draw.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/LightenDarken.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 1 )]
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="LightenDarken" type="OptionButton" parent="." index="4"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 102.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 122.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Lighten"
|
||||||
|
items = [ "Lighten", null, false, 0, null, "Darken", null, false, 1, null ]
|
||||||
|
selected = 0
|
||||||
|
|
||||||
|
[node name="Amount" type="VBoxContainer" parent="." index="5"]
|
||||||
|
margin_top = 126.0
|
||||||
|
margin_right = 116.0
|
||||||
|
margin_bottom = 188.0
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="Amount" index="0"]
|
||||||
|
margin_left = 30.0
|
||||||
|
margin_right = 85.0
|
||||||
|
margin_bottom = 14.0
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Amount:"
|
||||||
|
|
||||||
|
[node name="Spinbox" type="SpinBox" parent="Amount" index="1"]
|
||||||
|
margin_left = 21.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 95.0
|
||||||
|
margin_bottom = 42.0
|
||||||
|
hint_tooltip = "Lighten/Darken amount"
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
value = 10.0
|
||||||
|
align = 1
|
||||||
|
|
||||||
|
[node name="Slider" type="HSlider" parent="Amount" index="2"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 46.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 62.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
hint_tooltip = "Lighten/Darken amount"
|
||||||
|
focus_mode = 0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
size_flags_vertical = 1
|
||||||
|
value = 10.0
|
||||||
|
ticks_on_borders = true
|
||||||
|
|
||||||
|
[node name="EmptySpacer" parent="." index="7"]
|
||||||
|
margin_top = 192.0
|
||||||
|
margin_bottom = 204.0
|
||||||
|
|
||||||
|
[node name="Mirror" parent="." index="8"]
|
||||||
|
margin_top = 208.0
|
||||||
|
margin_bottom = 225.0
|
||||||
|
[connection signal="item_selected" from="LightenDarken" to="." method="_on_LightenDarken_item_selected"]
|
||||||
|
[connection signal="value_changed" from="Amount/Spinbox" to="." method="_on_LightenDarken_value_changed"]
|
||||||
|
[connection signal="value_changed" from="Amount/Slider" to="." method="_on_LightenDarken_value_changed"]
|
68
src/Tools/Pencil.gd
Normal file
68
src/Tools/Pencil.gd
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
extends "res://src/Tools/Draw.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _last_position := Vector2.INF
|
||||||
|
var _changed := false
|
||||||
|
|
||||||
|
|
||||||
|
class AlphaBlendOp extends Drawer.ColorOp:
|
||||||
|
var changed := false
|
||||||
|
|
||||||
|
|
||||||
|
func process(src: Color, dst: Color) -> Color:
|
||||||
|
changed = true
|
||||||
|
src.a *= strength
|
||||||
|
return dst.blend(src)
|
||||||
|
|
||||||
|
|
||||||
|
func _init() -> void:
|
||||||
|
_drawer.color_op = AlphaBlendOp.new()
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(position : Vector2) -> void:
|
||||||
|
update_mask()
|
||||||
|
_changed = false
|
||||||
|
_drawer.color_op.changed = false
|
||||||
|
|
||||||
|
prepare_undo()
|
||||||
|
_drawer.reset()
|
||||||
|
|
||||||
|
_draw_line = Tools.shift
|
||||||
|
if _draw_line:
|
||||||
|
_line_start = position
|
||||||
|
_line_end = position
|
||||||
|
update_line_polylines(_line_start, _line_end)
|
||||||
|
else:
|
||||||
|
draw_tool(position)
|
||||||
|
_last_position = position
|
||||||
|
Global.canvas.sprite_changed_this_frame = true
|
||||||
|
cursor_text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func draw_move(position : Vector2) -> void:
|
||||||
|
if _draw_line:
|
||||||
|
var d = _line_angle_constraint(_line_start, position)
|
||||||
|
_line_end = d.position
|
||||||
|
cursor_text = d.text
|
||||||
|
update_line_polylines(_line_start, _line_end)
|
||||||
|
else:
|
||||||
|
draw_fill_gap(_last_position, position)
|
||||||
|
_last_position = position
|
||||||
|
cursor_text = ""
|
||||||
|
Global.canvas.sprite_changed_this_frame = true
|
||||||
|
|
||||||
|
|
||||||
|
func draw_end(_position : Vector2) -> void:
|
||||||
|
if _draw_line:
|
||||||
|
draw_tool(_line_start)
|
||||||
|
draw_fill_gap(_line_start, _line_end)
|
||||||
|
_draw_line = false
|
||||||
|
if _changed or _drawer.color_op.changed:
|
||||||
|
commit_undo("Draw")
|
||||||
|
cursor_text = ""
|
||||||
|
update_random_image()
|
||||||
|
|
||||||
|
|
||||||
|
func _draw_brush_image(image : Image, src_rect: Rect2, dst: Vector2) -> void:
|
||||||
|
_changed = true
|
||||||
|
_get_draw_image().blend_rect(image, src_rect, dst)
|
7
src/Tools/Pencil.tscn
Normal file
7
src/Tools/Pencil.tscn
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Draw.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/Pencil.gd" type="Script" id=3]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 1 )]
|
||||||
|
script = ExtResource( 3 )
|
59
src/Tools/RectSelect.gd
Normal file
59
src/Tools/RectSelect.gd
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
extends "res://src/Tools/Base.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _start := Rect2(0, 0, 0, 0)
|
||||||
|
var _offset := Vector2.ZERO
|
||||||
|
var _drag := false
|
||||||
|
var _move := false
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(position : Vector2) -> void:
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
Global.selection_rectangle.move_end()
|
||||||
|
else:
|
||||||
|
Global.selection_rectangle.select_rect()
|
||||||
|
_drag = false
|
||||||
|
_move = false
|
||||||
|
cursor_text = ""
|
||||||
|
|
||||||
|
|
||||||
|
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 _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]
|
20
src/Tools/RectSelect.tscn
Normal file
20
src/Tools/RectSelect.tscn
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/RectSelect.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 1 )]
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="PixelPerfect" parent="." index="1"]
|
||||||
|
visible = false
|
||||||
|
|
||||||
|
[node name="EmptySpacer" parent="." index="2"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_bottom = 30.0
|
||||||
|
|
||||||
|
[node name="Mirror" parent="." index="3"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_bottom = 35.0
|
48
src/Tools/Zoom.gd
Normal file
48
src/Tools/Zoom.gd
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
extends "res://src/Tools/Base.gd"
|
||||||
|
|
||||||
|
|
||||||
|
var _zoom_mode := 0
|
||||||
|
|
||||||
|
|
||||||
|
func _on_ModeOptions_item_selected(id):
|
||||||
|
_zoom_mode = id
|
||||||
|
update_config()
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_FitToFrame_pressed():
|
||||||
|
Global.camera.fit_to_frame(Global.current_project.size)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_100_pressed():
|
||||||
|
Global.camera.zoom = Vector2.ONE
|
||||||
|
Global.camera.offset = Global.current_project.size / 2
|
||||||
|
Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %"
|
||||||
|
Global.horizontal_ruler.update()
|
||||||
|
Global.vertical_ruler.update()
|
||||||
|
|
||||||
|
|
||||||
|
func get_config() -> Dictionary:
|
||||||
|
return {
|
||||||
|
"zoom_mode" : _zoom_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func set_config(config : Dictionary) -> void:
|
||||||
|
_zoom_mode = config.get("zoom_mode", _zoom_mode)
|
||||||
|
|
||||||
|
|
||||||
|
func update_config() -> void:
|
||||||
|
$ModeOptions.selected = _zoom_mode
|
||||||
|
|
||||||
|
|
||||||
|
func draw_start(_position : Vector2) -> void:
|
||||||
|
Global.camera.zoom_camera(_zoom_mode * 2 - 1)
|
||||||
|
|
||||||
|
|
||||||
|
func draw_move(_position : Vector2) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func draw_end(_position : Vector2) -> void:
|
||||||
|
pass
|
75
src/Tools/Zoom.tscn
Normal file
75
src/Tools/Zoom.tscn
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=1]
|
||||||
|
[ext_resource path="res://src/Tools/Zoom.gd" type="Script" id=2]
|
||||||
|
|
||||||
|
[node name="ToolOptions" instance=ExtResource( 1 )]
|
||||||
|
script = ExtResource( 2 )
|
||||||
|
|
||||||
|
[node name="Mode" type="Label" parent="." index="1"]
|
||||||
|
margin_left = 38.0
|
||||||
|
margin_top = 18.0
|
||||||
|
margin_right = 78.0
|
||||||
|
margin_bottom = 32.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Mode:"
|
||||||
|
|
||||||
|
[node name="ModeOptions" type="OptionButton" parent="." index="2"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 36.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 56.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Zoom in"
|
||||||
|
items = [ "Zoom in", null, false, 0, null, "Zoom out", null, false, 1, null ]
|
||||||
|
selected = 0
|
||||||
|
|
||||||
|
[node name="Options" type="Label" parent="." index="3"]
|
||||||
|
margin_left = 30.0
|
||||||
|
margin_top = 60.0
|
||||||
|
margin_right = 85.0
|
||||||
|
margin_bottom = 74.0
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Options:"
|
||||||
|
|
||||||
|
[node name="FitToFrame" type="Button" parent="." index="4"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 78.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 98.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "Fit to frame"
|
||||||
|
|
||||||
|
[node name="100%" type="Button" parent="." index="5"]
|
||||||
|
margin_left = 12.0
|
||||||
|
margin_top = 102.0
|
||||||
|
margin_right = 104.0
|
||||||
|
margin_bottom = 122.0
|
||||||
|
rect_min_size = Vector2( 92, 0 )
|
||||||
|
mouse_default_cursor_shape = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "100% Zoom"
|
||||||
|
|
||||||
|
[node name="PixelPerfect" parent="." index="6"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 126.0
|
||||||
|
margin_bottom = 150.0
|
||||||
|
|
||||||
|
[node name="EmptySpacer" parent="." index="7"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 126.0
|
||||||
|
margin_bottom = 138.0
|
||||||
|
|
||||||
|
[node name="Mirror" parent="." index="8"]
|
||||||
|
visible = false
|
||||||
|
margin_top = 126.0
|
||||||
|
margin_bottom = 143.0
|
||||||
|
[connection signal="item_selected" from="ModeOptions" to="." method="_on_ModeOptions_item_selected"]
|
||||||
|
[connection signal="pressed" from="FitToFrame" to="." method="_on_FitToFrame_pressed"]
|
||||||
|
[connection signal="pressed" from="100%" to="." method="_on_100_pressed"]
|
|
@ -1,70 +1,29 @@
|
||||||
extends BaseButton
|
extends BaseButton
|
||||||
|
|
||||||
|
|
||||||
export var brush_type := 0 # Global.Brush_Types.PIXEL
|
var brush := Brushes.Brush.new()
|
||||||
export var custom_brush_index := -3
|
|
||||||
var random_brushes := []
|
|
||||||
|
|
||||||
|
|
||||||
func _on_BrushButton_pressed() -> void:
|
func _on_BrushButton_pressed() -> void:
|
||||||
# Delete the brush on middle mouse press
|
# Delete the brush on middle mouse press
|
||||||
if Input.is_action_just_released("middle_mouse"):
|
if Input.is_action_just_released("middle_mouse"):
|
||||||
_on_DeleteButton_pressed()
|
_on_DeleteButton_pressed()
|
||||||
return
|
|
||||||
|
|
||||||
# Change brush
|
|
||||||
Global.current_brush_types[Global.brush_type_window_position] = brush_type
|
|
||||||
Global.custom_brush_indexes[Global.brush_type_window_position] = custom_brush_index
|
|
||||||
if brush_type == Global.Brush_Types.FILE or brush_type == Global.Brush_Types.RANDOM_FILE or brush_type == Global.Brush_Types.CUSTOM:
|
|
||||||
if Global.current_tools[Global.brush_type_window_position] == Global.Tools.PENCIL:
|
|
||||||
Global.color_interpolation_containers[Global.brush_type_window_position].visible = true
|
|
||||||
else:
|
else:
|
||||||
Global.color_interpolation_containers[Global.brush_type_window_position].visible = false
|
Global.brushes_popup.select_brush(brush)
|
||||||
|
|
||||||
Global.update_custom_brush(Global.brush_type_window_position)
|
|
||||||
Global.brushes_popup.hide()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_DeleteButton_pressed() -> void:
|
func _on_DeleteButton_pressed() -> void:
|
||||||
if brush_type != Global.Brush_Types.CUSTOM:
|
if brush.type != Brushes.CUSTOM:
|
||||||
return
|
return
|
||||||
|
|
||||||
if Global.custom_brush_indexes[0] == custom_brush_index:
|
Global.brushes_popup.remove_brush(self)
|
||||||
Global.custom_brush_indexes[0] = -3
|
|
||||||
Global.current_brush_types[0] = Global.Brush_Types.PIXEL
|
|
||||||
Global.update_custom_brush(0)
|
|
||||||
if Global.custom_brush_indexes[1] == custom_brush_index:
|
|
||||||
Global.custom_brush_indexes[1] = -3
|
|
||||||
Global.current_brush_types[1] = Global.Brush_Types.PIXEL
|
|
||||||
Global.update_custom_brush(1)
|
|
||||||
|
|
||||||
Global.current_project.undos += 1
|
|
||||||
Global.current_project.undo_redo.create_action("Delete Custom Brush")
|
|
||||||
for i in range(Global.project_brush_container.get_child_count()):
|
|
||||||
var bb = Global.project_brush_container.get_child(i)
|
|
||||||
if Global.custom_brush_indexes[0] == bb.custom_brush_index:
|
|
||||||
Global.custom_brush_indexes[0] -= 1
|
|
||||||
if Global.custom_brush_indexes[1] == bb.custom_brush_index:
|
|
||||||
Global.custom_brush_indexes[1] -= 1
|
|
||||||
|
|
||||||
Global.current_project.undo_redo.add_do_property(bb, "custom_brush_index", bb.custom_brush_index - 1)
|
|
||||||
Global.current_project.undo_redo.add_undo_property(bb, "custom_brush_index", bb.custom_brush_index)
|
|
||||||
|
|
||||||
var custom_brushes: Array = Global.current_project.brushes.duplicate()
|
|
||||||
custom_brushes.remove(custom_brush_index)
|
|
||||||
|
|
||||||
Global.current_project.undo_redo.add_do_property(Global.current_project, "brushes", custom_brushes)
|
|
||||||
Global.current_project.undo_redo.add_undo_property(Global.current_project, "brushes", Global.current_project.brushes)
|
|
||||||
Global.current_project.undo_redo.add_do_method(Global, "redo_custom_brush", self)
|
|
||||||
Global.current_project.undo_redo.add_undo_method(Global, "undo_custom_brush", self)
|
|
||||||
Global.current_project.undo_redo.commit_action()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_BrushButton_mouse_entered() -> void:
|
func _on_BrushButton_mouse_entered() -> void:
|
||||||
if brush_type == Global.Brush_Types.CUSTOM:
|
if brush.type == Brushes.CUSTOM:
|
||||||
$DeleteButton.visible = true
|
$DeleteButton.visible = true
|
||||||
|
|
||||||
|
|
||||||
func _on_BrushButton_mouse_exited() -> void:
|
func _on_BrushButton_mouse_exited() -> void:
|
||||||
if brush_type == Global.Brush_Types.CUSTOM:
|
if brush.type == Brushes.CUSTOM:
|
||||||
$DeleteButton.visible = false
|
$DeleteButton.visible = false
|
||||||
|
|
132
src/UI/BrushesPopup.gd
Normal file
132
src/UI/BrushesPopup.gd
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
extends Popup
|
||||||
|
class_name Brushes
|
||||||
|
|
||||||
|
|
||||||
|
class Brush:
|
||||||
|
var type : int
|
||||||
|
var image : Image
|
||||||
|
var random := []
|
||||||
|
var index : int
|
||||||
|
|
||||||
|
|
||||||
|
signal brush_selected(brush)
|
||||||
|
signal brush_removed(brush)
|
||||||
|
enum {PIXEL, CIRCLE, FILLED_CIRCLE, FILE, RANDOM_FILE, CUSTOM}
|
||||||
|
|
||||||
|
var pixel_image = preload("res://assets/graphics/pixel_image.png")
|
||||||
|
var circle_image = preload("res://assets/graphics/circle_9x9.png")
|
||||||
|
var circle_filled_image = preload("res://assets/graphics/circle_filled_9x9.png")
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
var container = Global.brushes_popup.get_node("TabContainer/File/FileBrushContainer")
|
||||||
|
var button = create_button(pixel_image)
|
||||||
|
button.brush.type = PIXEL
|
||||||
|
button.hint_tooltip = "Pixel brush"
|
||||||
|
container.add_child(button)
|
||||||
|
button.brush.index = button.get_index()
|
||||||
|
|
||||||
|
button = create_button(circle_image)
|
||||||
|
button.brush.type = CIRCLE
|
||||||
|
button.hint_tooltip = "Circle brush"
|
||||||
|
container.add_child(button)
|
||||||
|
button.brush.index = button.get_index()
|
||||||
|
|
||||||
|
button = create_button(circle_filled_image)
|
||||||
|
button.brush.type = FILLED_CIRCLE
|
||||||
|
button.hint_tooltip = "Filled circle brush"
|
||||||
|
container.add_child(button)
|
||||||
|
button.brush.index = button.get_index()
|
||||||
|
|
||||||
|
|
||||||
|
func select_brush(brush : Brush) -> void:
|
||||||
|
emit_signal("brush_selected", brush)
|
||||||
|
hide()
|
||||||
|
|
||||||
|
|
||||||
|
static func get_default_brush() -> Brush:
|
||||||
|
var brush = Brush.new()
|
||||||
|
brush.type = PIXEL
|
||||||
|
brush.index = 0
|
||||||
|
return brush
|
||||||
|
|
||||||
|
|
||||||
|
static func create_button(image : Image) -> Node:
|
||||||
|
var button : BaseButton = load("res://src/UI/BrushButton.tscn").instance()
|
||||||
|
var tex := ImageTexture.new()
|
||||||
|
tex.create_from_image(image, 0)
|
||||||
|
button.get_child(0).texture = tex
|
||||||
|
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||||
|
return button
|
||||||
|
|
||||||
|
|
||||||
|
static func add_file_brush(images : Array, hint := "") -> void:
|
||||||
|
var button = create_button(images[0])
|
||||||
|
button.brush.type = FILE if images.size() == 1 else RANDOM_FILE
|
||||||
|
button.brush.image = images[0]
|
||||||
|
button.brush.random = images
|
||||||
|
button.hint_tooltip = hint
|
||||||
|
var container = Global.brushes_popup.get_node("TabContainer/File/FileBrushContainer")
|
||||||
|
container.add_child(button)
|
||||||
|
button.brush.index = button.get_index()
|
||||||
|
|
||||||
|
|
||||||
|
static func add_project_brush(image : Image) -> void:
|
||||||
|
var button = create_button(image)
|
||||||
|
button.brush.type = CUSTOM
|
||||||
|
button.brush.image = image
|
||||||
|
var container = Global.brushes_popup.get_node("TabContainer/Project/ProjectBrushContainer")
|
||||||
|
container.add_child(button)
|
||||||
|
button.brush.index = button.get_index()
|
||||||
|
|
||||||
|
|
||||||
|
static func clear_project_brush() -> void:
|
||||||
|
var container = Global.brushes_popup.get_node("TabContainer/Project/ProjectBrushContainer")
|
||||||
|
for child in container.get_children():
|
||||||
|
child.queue_free()
|
||||||
|
Global.brushes_popup.emit_signal("brush_removed", child.brush)
|
||||||
|
|
||||||
|
|
||||||
|
func get_brush(type : int, index : int) -> Brush:
|
||||||
|
var container
|
||||||
|
if type == CUSTOM:
|
||||||
|
container = Global.brushes_popup.get_node("TabContainer/Project/ProjectBrushContainer")
|
||||||
|
else:
|
||||||
|
container = Global.brushes_popup.get_node("TabContainer/File/FileBrushContainer")
|
||||||
|
var brush = get_default_brush()
|
||||||
|
if index < container.get_child_count():
|
||||||
|
brush = container.get_child(index).brush
|
||||||
|
return brush
|
||||||
|
|
||||||
|
|
||||||
|
func remove_brush(brush_button : Node) -> void:
|
||||||
|
emit_signal("brush_removed", brush_button.brush)
|
||||||
|
|
||||||
|
var project = Global.current_project
|
||||||
|
var undo_brushes = project.brushes.duplicate()
|
||||||
|
project.brushes.erase(brush_button.brush.image)
|
||||||
|
|
||||||
|
project.undos += 1
|
||||||
|
project.undo_redo.create_action("Delete Custom Brush")
|
||||||
|
project.undo_redo.add_do_property(project, "brushes", project.brushes)
|
||||||
|
project.undo_redo.add_undo_property(project, "brushes", undo_brushes)
|
||||||
|
project.undo_redo.add_do_method(self, "redo_custom_brush", brush_button)
|
||||||
|
project.undo_redo.add_undo_method(self, "undo_custom_brush", brush_button)
|
||||||
|
project.undo_redo.add_undo_reference(brush_button)
|
||||||
|
project.undo_redo.commit_action()
|
||||||
|
|
||||||
|
|
||||||
|
func undo_custom_brush(brush_button : BaseButton = null) -> void:
|
||||||
|
Global.general_undo()
|
||||||
|
var action_name : String = Global.current_project.undo_redo.get_current_action_name()
|
||||||
|
if action_name == "Delete Custom Brush":
|
||||||
|
$TabContainer/Project/ProjectBrushContainer.add_child(brush_button)
|
||||||
|
$TabContainer/Project/ProjectBrushContainer.move_child(brush_button, brush_button.brush.index)
|
||||||
|
brush_button.get_node("DeleteButton").visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func redo_custom_brush(brush_button : BaseButton = null) -> void:
|
||||||
|
Global.general_redo()
|
||||||
|
var action_name : String = Global.current_project.undo_redo.get_current_action_name()
|
||||||
|
if action_name == "Delete Custom Brush":
|
||||||
|
$TabContainer/Project/ProjectBrushContainer.remove_child(brush_button)
|
|
@ -1,41 +1,12 @@
|
||||||
[gd_scene load_steps=6 format=2]
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
[ext_resource path="res://src/UI/BrushButton.tscn" type="PackedScene" id=2]
|
[ext_resource path="res://src/UI/BrushesPopup.gd" type="Script" id=1]
|
||||||
|
|
||||||
[sub_resource type="Image" id=5]
|
|
||||||
data = {
|
|
||||||
"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0 ),
|
|
||||||
"format": "LumAlpha8",
|
|
||||||
"height": 9,
|
|
||||||
"mipmaps": false,
|
|
||||||
"width": 9
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="ImageTexture" id=2]
|
|
||||||
flags = 3
|
|
||||||
flags = 3
|
|
||||||
image = SubResource( 5 )
|
|
||||||
size = Vector2( 9, 9 )
|
|
||||||
|
|
||||||
[sub_resource type="Image" id=6]
|
|
||||||
data = {
|
|
||||||
"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0 ),
|
|
||||||
"format": "LumAlpha8",
|
|
||||||
"height": 9,
|
|
||||||
"mipmaps": false,
|
|
||||||
"width": 9
|
|
||||||
}
|
|
||||||
|
|
||||||
[sub_resource type="ImageTexture" id=4]
|
|
||||||
flags = 3
|
|
||||||
flags = 3
|
|
||||||
image = SubResource( 6 )
|
|
||||||
size = Vector2( 9, 9 )
|
|
||||||
|
|
||||||
[node name="BrushesPopup" type="Popup"]
|
[node name="BrushesPopup" type="Popup"]
|
||||||
margin_right = 226.0
|
margin_right = 226.0
|
||||||
margin_bottom = 144.0
|
margin_bottom = 144.0
|
||||||
rect_min_size = Vector2( 0, 144 )
|
rect_min_size = Vector2( 0, 144 )
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
|
||||||
[node name="TabContainer" type="TabContainer" parent="."]
|
[node name="TabContainer" type="TabContainer" parent="."]
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
@ -55,36 +26,8 @@ size_flags_horizontal = 3
|
||||||
scroll_horizontal_enabled = false
|
scroll_horizontal_enabled = false
|
||||||
|
|
||||||
[node name="FileBrushContainer" type="GridContainer" parent="TabContainer/File"]
|
[node name="FileBrushContainer" type="GridContainer" parent="TabContainer/File"]
|
||||||
margin_right = 104.0
|
|
||||||
margin_bottom = 32.0
|
|
||||||
columns = 6
|
columns = 6
|
||||||
|
|
||||||
[node name="PixelBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )]
|
|
||||||
hint_tooltip = "Pixel brush"
|
|
||||||
mouse_default_cursor_shape = 2
|
|
||||||
|
|
||||||
[node name="CircleBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )]
|
|
||||||
margin_left = 36.0
|
|
||||||
margin_right = 68.0
|
|
||||||
hint_tooltip = "Filled circle brush"
|
|
||||||
mouse_default_cursor_shape = 2
|
|
||||||
brush_type = 1
|
|
||||||
custom_brush_index = -2
|
|
||||||
|
|
||||||
[node name="BrushTexture" parent="TabContainer/File/FileBrushContainer/CircleBrushButton" index="0"]
|
|
||||||
texture = SubResource( 2 )
|
|
||||||
|
|
||||||
[node name="FilledCircleBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )]
|
|
||||||
margin_left = 72.0
|
|
||||||
margin_right = 104.0
|
|
||||||
hint_tooltip = "Circle brush"
|
|
||||||
mouse_default_cursor_shape = 2
|
|
||||||
brush_type = 2
|
|
||||||
custom_brush_index = -1
|
|
||||||
|
|
||||||
[node name="BrushTexture" parent="TabContainer/File/FileBrushContainer/FilledCircleBrushButton" index="0"]
|
|
||||||
texture = SubResource( 4 )
|
|
||||||
|
|
||||||
[node name="Project" type="ScrollContainer" parent="TabContainer"]
|
[node name="Project" type="ScrollContainer" parent="TabContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
|
@ -99,7 +42,3 @@ scroll_horizontal_enabled = false
|
||||||
|
|
||||||
[node name="ProjectBrushContainer" type="GridContainer" parent="TabContainer/Project"]
|
[node name="ProjectBrushContainer" type="GridContainer" parent="TabContainer/Project"]
|
||||||
columns = 5
|
columns = 5
|
||||||
|
|
||||||
[editable path="TabContainer/File/FileBrushContainer/CircleBrushButton"]
|
|
||||||
|
|
||||||
[editable path="TabContainer/File/FileBrushContainer/FilledCircleBrushButton"]
|
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
extends VBoxContainer
|
extends VBoxContainer
|
||||||
|
|
||||||
|
|
||||||
var previous_colors := [Color.black, Color.white]
|
onready var left_picker := $ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/LeftColorPickerButton
|
||||||
|
onready var right_picker := $ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/RightColorPickerButton
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
Tools.connect("color_changed", self, "update_color")
|
||||||
|
left_picker.get_picker().presets_visible = false
|
||||||
|
right_picker.get_picker().presets_visible = false
|
||||||
|
|
||||||
|
|
||||||
func _on_ColorSwitch_pressed() -> void:
|
func _on_ColorSwitch_pressed() -> void:
|
||||||
var temp : Color = Global.color_pickers[0].color
|
Tools.swap_color()
|
||||||
Global.color_pickers[0].color = Global.color_pickers[1].color
|
|
||||||
Global.color_pickers[1].color = temp
|
|
||||||
Global.update_custom_brush(0)
|
|
||||||
Global.update_custom_brush(1)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_ColorPickerButton_color_changed(color : Color, right : bool):
|
func _on_ColorPickerButton_color_changed(color : Color, right : bool):
|
||||||
var mouse_button := int(right)
|
var button := BUTTON_RIGHT if right else BUTTON_LEFT
|
||||||
# If the color changed while it's on full transparency, make it opaque (GH issue #54)
|
Tools.assign_color(color, button)
|
||||||
if color.a == 0:
|
|
||||||
if previous_colors[mouse_button].r != color.r or previous_colors[mouse_button].g != color.g or previous_colors[mouse_button].b != color.b:
|
|
||||||
Global.color_pickers[mouse_button].color.a = 1
|
|
||||||
Global.update_custom_brush(mouse_button)
|
|
||||||
previous_colors[mouse_button] = color
|
|
||||||
|
|
||||||
|
|
||||||
func _on_ColorPickerButton_pressed() -> void:
|
func _on_ColorPickerButton_pressed() -> void:
|
||||||
|
@ -31,108 +29,11 @@ func _on_ColorPickerButton_popup_closed() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _on_ColorDefaults_pressed() -> void:
|
func _on_ColorDefaults_pressed() -> void:
|
||||||
Global.color_pickers[0].color = Color.black
|
Tools.default_color()
|
||||||
Global.color_pickers[1].color = Color.white
|
|
||||||
Global.update_custom_brush(0)
|
|
||||||
Global.update_custom_brush(1)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_FitToFrameButton_pressed() -> void:
|
func update_color(color : Color, button : int) -> void:
|
||||||
Global.camera.fit_to_frame(Global.current_project.size)
|
if button == BUTTON_LEFT:
|
||||||
|
left_picker.color = color
|
||||||
|
|
||||||
func _on_100ZoomButton_pressed() -> void:
|
|
||||||
Global.camera.zoom = Vector2.ONE
|
|
||||||
Global.camera.offset = Global.current_project.size / 2
|
|
||||||
Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %"
|
|
||||||
Global.horizontal_ruler.update()
|
|
||||||
Global.vertical_ruler.update()
|
|
||||||
|
|
||||||
|
|
||||||
func _on_BrushTypeButton_pressed(right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.brushes_popup.popup(Rect2(Global.brush_type_buttons[mouse_button].rect_global_position, Vector2(226, 72)))
|
|
||||||
Global.brush_type_window_position = mouse_button
|
|
||||||
|
|
||||||
|
|
||||||
func _on_BrushSizeEdit_value_changed(value : float, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
var new_size = int(value)
|
|
||||||
Global.brush_size_edits[mouse_button].value = value
|
|
||||||
Global.brush_size_sliders[mouse_button].value = value
|
|
||||||
Global.brush_sizes[mouse_button] = new_size
|
|
||||||
Global.update_custom_brush(mouse_button)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_PixelPerfectMode_toggled(button_pressed : bool, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.pixel_perfect[mouse_button] = button_pressed
|
|
||||||
|
|
||||||
|
|
||||||
func _on_InterpolateFactor_value_changed(value : float, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.interpolate_spinboxes[mouse_button].value = value
|
|
||||||
Global.interpolate_sliders[mouse_button].value = value
|
|
||||||
Global.update_custom_brush(mouse_button)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_FillAreaOptions_item_selected(ID : int, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.fill_areas[mouse_button] = ID
|
|
||||||
|
|
||||||
|
|
||||||
func _on_FillWithOptions_item_selected(ID : int, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.fill_with[mouse_button] = ID
|
|
||||||
if ID == 1:
|
|
||||||
Global.fill_pattern_containers[mouse_button].visible = true
|
|
||||||
else:
|
else:
|
||||||
Global.fill_pattern_containers[mouse_button].visible = false
|
right_picker.color = color
|
||||||
|
|
||||||
|
|
||||||
func _on_PatternTypeButton_pressed(right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.pattern_window_position = mouse_button
|
|
||||||
Global.patterns_popup.popup(Rect2(Global.brush_type_buttons[mouse_button].rect_global_position, Vector2(226, 72)))
|
|
||||||
|
|
||||||
|
|
||||||
func _on_PatternOffsetX_value_changed(value : float, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.fill_pattern_offsets[mouse_button].x = value
|
|
||||||
|
|
||||||
|
|
||||||
func _on_PatternOffsetY_value_changed(value : float, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.fill_pattern_offsets[mouse_button].y = value
|
|
||||||
|
|
||||||
|
|
||||||
func _on_LightenDarken_item_selected(ID : int, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.ld_modes[mouse_button] = ID
|
|
||||||
|
|
||||||
|
|
||||||
func _on_LDAmount_value_changed(value : float, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.ld_amounts[mouse_button] = value / 100
|
|
||||||
Global.ld_amount_sliders[mouse_button].value = value
|
|
||||||
Global.ld_amount_spinboxes[mouse_button].value = value
|
|
||||||
|
|
||||||
|
|
||||||
func _on_ForColorOptions_item_selected(ID : int, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.color_picker_for[mouse_button] = ID
|
|
||||||
|
|
||||||
|
|
||||||
func _on_ZoomModeOptions_item_selected(ID : int, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.zoom_modes[mouse_button] = ID
|
|
||||||
|
|
||||||
|
|
||||||
func _on_HorizontalMirroring_toggled(button_pressed : bool, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.horizontal_mirror[mouse_button] = button_pressed
|
|
||||||
|
|
||||||
|
|
||||||
func _on_VerticalMirroring_toggled(button_pressed : bool, right : bool) -> void:
|
|
||||||
var mouse_button := int(right)
|
|
||||||
Global.vertical_mirror[mouse_button] = button_pressed
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,22 +1,8 @@
|
||||||
extends TextureButton
|
extends TextureButton
|
||||||
|
|
||||||
|
|
||||||
var image : Image
|
var pattern := Patterns.Pattern.new()
|
||||||
var image_size : Vector2
|
|
||||||
var texture : ImageTexture
|
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
|
||||||
if image:
|
|
||||||
image_size = image.get_size()
|
|
||||||
texture = ImageTexture.new()
|
|
||||||
texture.create_from_image(image, 0)
|
|
||||||
|
|
||||||
|
|
||||||
func _on_PatternButton_pressed() -> void:
|
func _on_PatternButton_pressed() -> void:
|
||||||
Global.pattern_images[Global.pattern_window_position] = image
|
Global.patterns_popup.select_pattern(pattern)
|
||||||
Global.fill_pattern_containers[Global.pattern_window_position].get_child(0).get_child(0).texture = texture
|
|
||||||
Global.fill_pattern_containers[Global.pattern_window_position].get_child(2).get_child(1).max_value = image_size.x - 1
|
|
||||||
Global.fill_pattern_containers[Global.pattern_window_position].get_child(3).get_child(1).max_value = image_size.y - 1
|
|
||||||
|
|
||||||
Global.patterns_popup.hide()
|
|
||||||
|
|
45
src/UI/PatternsPopup.gd
Normal file
45
src/UI/PatternsPopup.gd
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
extends PopupPanel
|
||||||
|
class_name Patterns
|
||||||
|
|
||||||
|
|
||||||
|
class Pattern:
|
||||||
|
var image : Image
|
||||||
|
var index : int
|
||||||
|
|
||||||
|
signal pattern_selected(pattern)
|
||||||
|
|
||||||
|
var default_pattern : Pattern = null
|
||||||
|
|
||||||
|
|
||||||
|
func select_pattern(pattern : Pattern) -> void:
|
||||||
|
emit_signal("pattern_selected", pattern)
|
||||||
|
hide()
|
||||||
|
|
||||||
|
|
||||||
|
static func create_button(image : Image) -> Node:
|
||||||
|
var button : BaseButton = load("res://src/UI/PatternButton.tscn").instance()
|
||||||
|
var tex := ImageTexture.new()
|
||||||
|
tex.create_from_image(image, 0)
|
||||||
|
button.get_child(0).texture = tex
|
||||||
|
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||||||
|
return button
|
||||||
|
|
||||||
|
|
||||||
|
static func add(image : Image, hint := "") -> void:
|
||||||
|
var button = create_button(image)
|
||||||
|
button.pattern.image = image
|
||||||
|
button.hint_tooltip = hint
|
||||||
|
var container = Global.patterns_popup.get_node("ScrollContainer/PatternContainer")
|
||||||
|
container.add_child(button)
|
||||||
|
button.pattern.index = button.get_index()
|
||||||
|
|
||||||
|
if Global.patterns_popup.default_pattern == null:
|
||||||
|
Global.patterns_popup.default_pattern = button.pattern
|
||||||
|
|
||||||
|
|
||||||
|
func get_pattern(index : int) -> Pattern:
|
||||||
|
var container = Global.patterns_popup.get_node("ScrollContainer/PatternContainer")
|
||||||
|
var pattern = default_pattern
|
||||||
|
if index < container.get_child_count():
|
||||||
|
pattern = container.get_child(index).pattern
|
||||||
|
return pattern
|
|
@ -1,8 +1,11 @@
|
||||||
[gd_scene format=2]
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://src/UI/PatternsPopup.gd" type="Script" id=1]
|
||||||
|
|
||||||
[node name="PatternsPopup" type="PopupPanel"]
|
[node name="PatternsPopup" type="PopupPanel"]
|
||||||
margin_right = 226.0
|
margin_right = 226.0
|
||||||
margin_bottom = 104.0
|
margin_bottom = 104.0
|
||||||
|
script = ExtResource( 1 )
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,98 +1,40 @@
|
||||||
extends VBoxContainer
|
extends VBoxContainer
|
||||||
|
|
||||||
|
|
||||||
var tools := []
|
# Node, shortcut
|
||||||
|
onready var tools := [
|
||||||
|
[$RectSelect, "rectangle_select"],
|
||||||
|
[$Zoom, "zoom"],
|
||||||
|
[$ColorPicker, "colorpicker"],
|
||||||
|
[$Pencil, "pencil"],
|
||||||
|
[$Eraser, "eraser"],
|
||||||
|
[$Bucket, "fill"],
|
||||||
|
[$LightenDarken, "lightdark"],
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
# Node, left mouse shortcut, right mouse shortcut
|
|
||||||
tools.append([Global.find_node_by_name(self, "Pencil"), "left_pencil_tool", "right_pencil_tool"])
|
|
||||||
tools.append([Global.find_node_by_name(self, "Eraser"), "left_eraser_tool", "right_eraser_tool"])
|
|
||||||
tools.append([Global.find_node_by_name(self, "Bucket"), "left_fill_tool", "right_fill_tool"])
|
|
||||||
tools.append([Global.find_node_by_name(self, "LightenDarken"), "left_lightdark_tool", "right_lightdark_tool"])
|
|
||||||
tools.append([Global.find_node_by_name(self, "RectSelect"), "left_rectangle_select_tool", "right_rectangle_select_tool"])
|
|
||||||
tools.append([Global.find_node_by_name(self, "ColorPicker"), "left_colorpicker_tool", "right_colorpicker_tool"])
|
|
||||||
tools.append([Global.find_node_by_name(self, "Zoom"), "left_zoom_tool", "right_zoom_tool"])
|
|
||||||
|
|
||||||
for t in tools:
|
for t in tools:
|
||||||
t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]])
|
t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]])
|
||||||
|
|
||||||
Global.update_hint_tooltips()
|
Global.update_hint_tooltips()
|
||||||
|
|
||||||
|
|
||||||
func _input(event : InputEvent) -> void:
|
func _input(event : InputEvent) -> void:
|
||||||
if Global.has_focus:
|
if not Global.has_focus:
|
||||||
if event.is_action_pressed("undo") or event.is_action_pressed("redo") or event.is_action_pressed("redo_secondary"):
|
return
|
||||||
|
for action in ["undo", "redo", "redo_secondary"]:
|
||||||
|
if event.is_action_pressed(action):
|
||||||
return
|
return
|
||||||
for t in tools: # Handle tool shortcuts
|
for t in tools: # Handle tool shortcuts
|
||||||
if event.is_action_pressed(t[2]): # Shortcut for right button (with Alt)
|
if event.is_action_pressed("right_" + t[1] + "_tool"): # Shortcut for right button (with Alt)
|
||||||
_on_Tool_pressed(t[0], false, false)
|
Tools.assign_tool(t[0].name, BUTTON_RIGHT)
|
||||||
elif event.is_action_pressed(t[1]): # Shortcut for left button
|
elif event.is_action_pressed("left_" + t[1] + "_tool"): # Shortcut for left button
|
||||||
_on_Tool_pressed(t[0], false, true)
|
Tools.assign_tool(t[0].name, BUTTON_LEFT)
|
||||||
|
|
||||||
|
|
||||||
func _on_Tool_pressed(tool_pressed : BaseButton, mouse_press := true, key_for_left := true) -> void:
|
func _on_Tool_pressed(tool_pressed : BaseButton) -> void:
|
||||||
var current_action := tool_pressed.name
|
var button := -1
|
||||||
var current_tool : int = Global.Tools.keys().find(current_action.to_upper())
|
button = BUTTON_LEFT if Input.is_action_just_released("left_mouse") else button
|
||||||
var left_tool_name := str(Global.Tools.keys()[Global.current_tools[0]]).to_lower()
|
button = BUTTON_RIGHT if Input.is_action_just_released("right_mouse") else button
|
||||||
var right_tool_name := str(Global.Tools.keys()[Global.current_tools[1]]).to_lower()
|
if button != -1:
|
||||||
var current_mouse_button := -1
|
Tools.assign_tool(tool_pressed.name, button)
|
||||||
|
|
||||||
if (mouse_press and Input.is_action_just_released("left_mouse")) or (!mouse_press and key_for_left):
|
|
||||||
left_tool_name = current_action.to_lower()
|
|
||||||
current_mouse_button = Global.Mouse_Button.LEFT
|
|
||||||
|
|
||||||
elif (mouse_press and Input.is_action_just_released("right_mouse")) or (!mouse_press and !key_for_left):
|
|
||||||
right_tool_name = current_action.to_lower()
|
|
||||||
current_mouse_button = Global.Mouse_Button.RIGHT
|
|
||||||
|
|
||||||
if current_mouse_button != -1:
|
|
||||||
Global.current_tools[current_mouse_button] = current_tool
|
|
||||||
# Start from 1, so the label won't get invisible
|
|
||||||
for i in range(1, Global.tool_options_containers[current_mouse_button].get_child_count()):
|
|
||||||
Global.tool_options_containers[current_mouse_button].get_child(i).visible = false
|
|
||||||
|
|
||||||
Global.tool_options_containers[current_mouse_button].get_node("EmptySpacer").visible = true
|
|
||||||
|
|
||||||
# Tool options visible depending on the selected tool
|
|
||||||
if current_tool == Global.Tools.PENCIL:
|
|
||||||
Global.brush_type_containers[current_mouse_button].visible = true
|
|
||||||
Global.brush_size_sliders[current_mouse_button].visible = true
|
|
||||||
Global.pixel_perfect_containers[current_mouse_button].visible = true
|
|
||||||
Global.mirror_containers[current_mouse_button].visible = true
|
|
||||||
if Global.current_brush_types[current_mouse_button] == Global.Brush_Types.FILE or Global.current_brush_types[current_mouse_button] == Global.Brush_Types.CUSTOM or Global.current_brush_types[current_mouse_button] == Global.Brush_Types.RANDOM_FILE:
|
|
||||||
Global.color_interpolation_containers[current_mouse_button].visible = true
|
|
||||||
elif current_tool == Global.Tools.ERASER:
|
|
||||||
Global.brush_type_containers[current_mouse_button].visible = true
|
|
||||||
Global.brush_size_sliders[current_mouse_button].visible = true
|
|
||||||
Global.pixel_perfect_containers[current_mouse_button].visible = true
|
|
||||||
Global.mirror_containers[current_mouse_button].visible = true
|
|
||||||
elif current_tool == Global.Tools.BUCKET:
|
|
||||||
Global.fill_area_containers[current_mouse_button].visible = true
|
|
||||||
Global.mirror_containers[current_mouse_button].visible = true
|
|
||||||
elif current_tool == Global.Tools.LIGHTENDARKEN:
|
|
||||||
Global.brush_type_containers[current_mouse_button].visible = true
|
|
||||||
Global.brush_size_sliders[current_mouse_button].visible = true
|
|
||||||
Global.pixel_perfect_containers[current_mouse_button].visible = true
|
|
||||||
Global.ld_containers[current_mouse_button].visible = true
|
|
||||||
Global.mirror_containers[current_mouse_button].visible = true
|
|
||||||
elif current_tool == Global.Tools.COLORPICKER:
|
|
||||||
Global.colorpicker_containers[current_mouse_button].visible = true
|
|
||||||
elif current_tool == Global.Tools.ZOOM:
|
|
||||||
Global.zoom_containers[current_mouse_button].visible = true
|
|
||||||
|
|
||||||
for t in tools:
|
|
||||||
var tool_name : String = t[0].name.to_lower()
|
|
||||||
var texture_button : TextureRect = t[0].get_child(0)
|
|
||||||
|
|
||||||
if tool_name == left_tool_name and tool_name == right_tool_name:
|
|
||||||
Global.change_button_texturerect(texture_button, "%s_l_r.png" % tool_name.to_lower())
|
|
||||||
elif tool_name == left_tool_name:
|
|
||||||
Global.change_button_texturerect(texture_button, "%s_l.png" % tool_name.to_lower())
|
|
||||||
elif tool_name == right_tool_name:
|
|
||||||
Global.change_button_texturerect(texture_button, "%s_r.png" % tool_name.to_lower())
|
|
||||||
else:
|
|
||||||
Global.change_button_texturerect(texture_button, "%s.png" % tool_name.to_lower())
|
|
||||||
|
|
||||||
Global.left_cursor_tool_texture.create_from_image(load("res://assets/graphics/cursor_icons/%s_cursor.png" % left_tool_name), 0)
|
|
||||||
Global.right_cursor_tool_texture.create_from_image(load("res://assets/graphics/cursor_icons/%s_cursor.png" % right_tool_name), 0)
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ func setup_edit_menu() -> void:
|
||||||
var edit_menu_items := {
|
var edit_menu_items := {
|
||||||
"Undo" : InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(),
|
"Undo" : InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(),
|
||||||
"Redo" : InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(),
|
"Redo" : InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(),
|
||||||
|
"Copy" : InputMap.get_action_list("copy")[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" : 0,
|
"Clear Selection" : 0,
|
||||||
"Preferences" : 0
|
"Preferences" : 0
|
||||||
}
|
}
|
||||||
|
@ -201,15 +204,16 @@ func edit_menu_id_pressed(id : int) -> void:
|
||||||
Global.control.redone = true
|
Global.control.redone = true
|
||||||
Global.current_project.undo_redo.redo()
|
Global.current_project.undo_redo.redo()
|
||||||
Global.control.redone = false
|
Global.control.redone = false
|
||||||
2: # Clear selection
|
2: # Copy
|
||||||
Global.canvas.handle_undo("Rectangle Select")
|
Global.selection_rectangle.copy()
|
||||||
Global.selection_rectangle.polygon[0] = Vector2.ZERO
|
3: # paste
|
||||||
Global.selection_rectangle.polygon[1] = Vector2.ZERO
|
Global.selection_rectangle.paste()
|
||||||
Global.selection_rectangle.polygon[2] = Vector2.ZERO
|
4: # Delete
|
||||||
Global.selection_rectangle.polygon[3] = Vector2.ZERO
|
Global.selection_rectangle.delete()
|
||||||
Global.current_project.selected_pixels.clear()
|
5: # Clear selection
|
||||||
Global.canvas.handle_redo("Rectangle Select")
|
Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0))
|
||||||
3: # Preferences
|
Global.selection_rectangle.select_rect()
|
||||||
|
6: # Preferences
|
||||||
Global.preferences_dialog.popup_centered(Vector2(400, 280))
|
Global.preferences_dialog.popup_centered(Vector2(400, 280))
|
||||||
Global.dialog_open(true)
|
Global.dialog_open(true)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue