mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00: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
|
@ -97,6 +97,15 @@ msgstr ""
|
|||
msgid "Redo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Paste"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Scale Image"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ _global_script_classes=[ {
|
|||
"language": "GDScript",
|
||||
"path": "res://src/Classes/AnimationTag.gd"
|
||||
}, {
|
||||
"base": "Popup",
|
||||
"class": "Brushes",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/UI/BrushesPopup.gd"
|
||||
}, {
|
||||
"base": "Node2D",
|
||||
"class": "Canvas",
|
||||
"language": "GDScript",
|
||||
|
@ -59,6 +64,11 @@ _global_script_classes=[ {
|
|||
"language": "GDScript",
|
||||
"path": "res://src/Palette/PaletteColor.gd"
|
||||
}, {
|
||||
"base": "PopupPanel",
|
||||
"class": "Patterns",
|
||||
"language": "GDScript",
|
||||
"path": "res://src/UI/PatternsPopup.gd"
|
||||
}, {
|
||||
"base": "Reference",
|
||||
"class": "Project",
|
||||
"language": "GDScript",
|
||||
|
@ -66,6 +76,7 @@ _global_script_classes=[ {
|
|||
} ]
|
||||
_global_script_class_icons={
|
||||
"AnimationTag": "",
|
||||
"Brushes": "",
|
||||
"Canvas": "",
|
||||
"Cel": "",
|
||||
"Drawer": "",
|
||||
|
@ -75,6 +86,7 @@ _global_script_class_icons={
|
|||
"LayerButton": "",
|
||||
"Palette": "",
|
||||
"PaletteColor": "",
|
||||
"Patterns": "",
|
||||
"Project": ""
|
||||
}
|
||||
|
||||
|
@ -97,6 +109,7 @@ Global="*res://src/Autoload/Global.gd"
|
|||
Import="*res://src/Autoload/Import.gd"
|
||||
OpenSave="*res://src/Autoload/OpenSave.gd"
|
||||
DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd"
|
||||
Tools="*res://src/Autoload/Tools.gd"
|
||||
Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd"
|
||||
|
||||
[debug]
|
||||
|
|
|
@ -1,336 +1,6 @@
|
|||
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:
|
||||
var scaled = Image.new()
|
||||
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.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 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 Mouse_Button {LEFT, RIGHT}
|
||||
enum Tools {PENCIL, ERASER, BUCKET, LIGHTENDARKEN, RECTSELECT, COLORPICKER, ZOOM}
|
||||
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 ^.^
|
||||
const low_speed_move_rate := 150.0
|
||||
|
@ -44,8 +37,8 @@ var pressure_sensitivity_mode = Pressure_Sensitivity.NONE
|
|||
var open_last_project := false
|
||||
var smooth_zoom := true
|
||||
var cursor_image = preload("res://assets/graphics/cursor_icons/cursor.png")
|
||||
var left_cursor_tool_texture : ImageTexture
|
||||
var right_cursor_tool_texture : ImageTexture
|
||||
var left_cursor_tool_texture := ImageTexture.new()
|
||||
var right_cursor_tool_texture := ImageTexture.new()
|
||||
|
||||
var image_clipboard : Image
|
||||
var play_only_tags := true
|
||||
|
@ -68,27 +61,11 @@ var autosave_interval := 5.0
|
|||
var enable_autosave := true
|
||||
|
||||
# Tools & options
|
||||
var current_tools := [Tools.PENCIL, Tools.ERASER]
|
||||
var show_left_tool_icon := true
|
||||
var show_right_tool_icon := true
|
||||
var left_square_indicator_visible := true
|
||||
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
|
||||
var tile_mode := 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_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
|
||||
var palettes := {}
|
||||
|
||||
|
@ -158,41 +116,11 @@ var export_dialog : AcceptDialog
|
|||
var preferences_dialog : AcceptDialog
|
||||
var unsaved_changes_dialog : ConfirmationDialog
|
||||
|
||||
var color_pickers := []
|
||||
|
||||
var color_switch_button : BaseButton
|
||||
|
||||
var tool_options_containers := []
|
||||
|
||||
var brush_type_containers := []
|
||||
var brush_type_buttons := []
|
||||
var brushes_popup : Popup
|
||||
var file_brush_container : GridContainer
|
||||
var project_brush_container : GridContainer
|
||||
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_timer : Timer
|
||||
|
@ -251,21 +179,6 @@ func _ready() -> void:
|
|||
right_cursor = find_node_by_name(root, "RightCursor")
|
||||
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")
|
||||
main_viewport = find_node_by_name(root, "ViewportContainer")
|
||||
second_viewport = find_node_by_name(root, "ViewportContainer2")
|
||||
|
@ -294,58 +207,11 @@ func _ready() -> void:
|
|||
preferences_dialog = find_node_by_name(root, "PreferencesDialog")
|
||||
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")
|
||||
|
||||
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")
|
||||
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")
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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:
|
||||
config_cache.set_value("window", "screen", OS.current_screen)
|
||||
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
|
||||
# to use.
|
||||
# take initial image...
|
||||
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)
|
||||
Brushes.add_file_brush(loaded_images, tooltip_name)
|
||||
|
||||
# Add a plain brush from the given path to the list of brushes.
|
||||
# Taken, again, from find_brushes
|
||||
|
@ -127,8 +116,7 @@ func add_plain_brush(path: String, tooltip_name: String) -> void:
|
|||
return
|
||||
# do the standard conversion thing...
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
Global.file_brushes.append(image)
|
||||
Global.create_brush_button(image, Global.Brush_Types.FILE, tooltip_name)
|
||||
Brushes.add_file_brush([image], tooltip_name)
|
||||
|
||||
|
||||
# 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
|
||||
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:
|
||||
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))
|
||||
if err == OK:
|
||||
image.convert(Image.FORMAT_RGBA8)
|
||||
Global.patterns.append(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
|
||||
Global.patterns_popup.add(image)
|
||||
|
||||
|
||||
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()
|
||||
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
|
||||
new_project.brushes.append(image)
|
||||
Global.create_brush_button(image)
|
||||
Brushes.add_project_brush(image)
|
||||
|
||||
file.close()
|
||||
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()
|
||||
|
||||
# Load tool options
|
||||
Global.color_pickers[0].color = file.get_var()
|
||||
Global.color_pickers[1].color = file.get_var()
|
||||
Global.brush_sizes[0] = file.get_8()
|
||||
Global.brush_size_edits[0].value = Global.brush_sizes[0]
|
||||
Global.brush_sizes[1] = file.get_8()
|
||||
Global.brush_size_edits[1].value = Global.brush_sizes[1]
|
||||
file.get_var()
|
||||
file.get_var()
|
||||
file.get_8()
|
||||
file.get_8()
|
||||
if file_major_version == 0 and file_minor_version < 7:
|
||||
var left_palette = file.get_var()
|
||||
var right_palette = 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)
|
||||
file.get_var()
|
||||
file.get_var()
|
||||
|
||||
# Load custom brushes
|
||||
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()
|
||||
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
|
||||
new_project.brushes.append(image)
|
||||
Global.create_brush_button(image)
|
||||
Brushes.add_project_brush(image)
|
||||
brush_line = file.get_line()
|
||||
|
||||
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
|
352
src/Canvas.gd
352
src/Canvas.gd
|
@ -5,17 +5,9 @@ extends Node2D
|
|||
var location := Vector2.ZERO
|
||||
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 previous_mouse_pos := Vector2.ZERO
|
||||
var previous_mouse_pos_for_lines := Vector2.ZERO
|
||||
var can_undo := true
|
||||
var cursor_image_has_changed := false
|
||||
var previous_action := -1
|
||||
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.
|
||||
|
@ -23,7 +15,6 @@ func _ready() -> void:
|
|||
var frame : Frame = new_empty_frame(true)
|
||||
Global.current_project.frames.append(frame)
|
||||
camera_zoom()
|
||||
line_pos = [previous_mouse_pos_for_lines, previous_mouse_pos_for_lines]
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
|
@ -54,111 +45,40 @@ func _draw() -> void:
|
|||
draw_grid(Global.grid_type)
|
||||
|
||||
# Draw rectangle to indicate the pixel currently being hovered on
|
||||
if Global.can_draw:
|
||||
var mouse_pos := current_pixel
|
||||
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)
|
||||
if Global.has_focus and Global.can_draw:
|
||||
Tools.draw_indicator()
|
||||
|
||||
|
||||
func _input(event : InputEvent) -> void:
|
||||
# Don't process anything below if the input isn't a mouse event, or Shift/Ctrl.
|
||||
# This decreases CPU/GPU usage slightly.
|
||||
if not event is InputEventMouse:
|
||||
if event is InputEventKey:
|
||||
if event.scancode != KEY_SHIFT && event.scancode != KEY_CONTROL:
|
||||
return
|
||||
else:
|
||||
if not event is InputEventKey:
|
||||
return
|
||||
|
||||
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")):
|
||||
made_line = false
|
||||
DrawingAlgos.reset()
|
||||
can_undo = true
|
||||
elif not event.scancode in [KEY_SHIFT, KEY_CONTROL]:
|
||||
return
|
||||
# elif not get_viewport_rect().has_point(event.position):
|
||||
# return
|
||||
|
||||
current_pixel = get_local_mouse_position() + location
|
||||
|
||||
if Global.has_focus:
|
||||
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
|
||||
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_max = location.x + current_project.size.x
|
||||
current_project.y_min = location.y
|
||||
current_project.y_max = location.y + current_project.size.y
|
||||
if current_project.selected_pixels.size() != 0:
|
||||
current_project.x_min = max(current_project.x_min, Global.selection_rectangle.polygon[0].x)
|
||||
current_project.x_max = min(current_project.x_max, Global.selection_rectangle.polygon[2].x)
|
||||
current_project.y_min = max(current_project.y_min, Global.selection_rectangle.polygon[0].y)
|
||||
current_project.y_max = min(current_project.y_max, Global.selection_rectangle.polygon[2].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 not current_project.selected_rect.has_no_area():
|
||||
current_project.x_min = max(current_project.x_min, current_project.selected_rect.position.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, current_project.selected_rect.position.y)
|
||||
current_project.y_max = min(current_project.y_max, current_project.selected_rect.end.y)
|
||||
|
||||
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:
|
||||
cursor_image_has_changed = true
|
||||
if Global.show_left_tool_icon:
|
||||
|
@ -166,80 +86,13 @@ func _input(event : InputEvent) -> void:
|
|||
if Global.show_right_tool_icon:
|
||||
Global.right_cursor.visible = true
|
||||
else:
|
||||
Global.cursor_position_label.text = "[%s×%s]" % [current_project.size.x, current_project.size.y]
|
||||
if cursor_image_has_changed:
|
||||
cursor_image_has_changed = false
|
||||
Global.left_cursor.visible = false
|
||||
Global.right_cursor.visible = false
|
||||
|
||||
# Handle Undo/Redo
|
||||
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"))
|
||||
Tools.handle_draw(current_pixel.floor(), event)
|
||||
|
||||
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:
|
||||
update_texture(current_project.current_layer)
|
||||
|
||||
|
@ -284,143 +137,6 @@ func new_empty_frame(first_time := false, single_layer := false, size := Global.
|
|||
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:
|
||||
if !can_undo:
|
||||
return
|
||||
|
@ -442,16 +158,12 @@ func handle_undo(action : String) -> void:
|
|||
var data = f.cels[Global.current_project.current_layer].image.data
|
||||
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)
|
||||
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)
|
||||
|
||||
can_undo = false
|
||||
|
||||
|
||||
func handle_redo(action : String) -> void:
|
||||
func handle_redo(_action : String) -> void:
|
||||
can_undo = true
|
||||
|
||||
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
|
||||
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)
|
||||
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.commit_action()
|
||||
|
||||
|
@ -561,34 +270,3 @@ func draw_grid(grid_type : int) -> void:
|
|||
for x in range(0, size.x, Global.grid_height * 2):
|
||||
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)
|
||||
|
||||
|
||||
# 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 ColorOp:
|
||||
var strength := 1.0
|
||||
|
||||
|
||||
func process(src: Color, _dst: Color) -> Color:
|
||||
return src
|
||||
|
||||
|
||||
class SimpleDrawer:
|
||||
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
|
||||
_sprite.set_pixel(_pos.x, _pos.y, _new_color)
|
||||
func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void:
|
||||
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:
|
||||
|
@ -15,9 +27,10 @@ class PixelPerfectDrawer:
|
|||
last_pixels = [null, null]
|
||||
|
||||
|
||||
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
|
||||
last_pixels.push_back([_pos, _sprite.get_pixel(_pos.x, _pos.y)])
|
||||
_sprite.set_pixel(_pos.x, _pos.y, _new_color)
|
||||
func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void:
|
||||
var color_old = image.get_pixelv(position)
|
||||
last_pixels.push_back([position, color_old])
|
||||
image.set_pixelv(position, op.process(color, color_old))
|
||||
|
||||
var corner = last_pixels.pop_front()
|
||||
var neighbour = last_pixels[0]
|
||||
|
@ -25,14 +38,15 @@ class PixelPerfectDrawer:
|
|||
if corner == null or neighbour == null:
|
||||
return
|
||||
|
||||
if _pos - corner[0] in corners and _pos - neighbour[0] in neighbours:
|
||||
_sprite.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
|
||||
if position - corner[0] in corners and position - neighbour[0] in neighbours:
|
||||
image.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
|
||||
last_pixels[0] = corner
|
||||
|
||||
|
||||
var pixel_perfect := false setget set_pixel_perfect
|
||||
var h_mirror := false
|
||||
var v_mirror := false
|
||||
var horizontal_mirror := false
|
||||
var vertical_mirror := false
|
||||
var color_op := ColorOp.new()
|
||||
|
||||
var simple_drawer := SimpleDrawer.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]
|
||||
|
||||
|
||||
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
|
||||
var mirror_x = Global.current_project.x_max + Global.current_project.x_min - _pos.x - 1
|
||||
var mirror_y = Global.current_project.y_max + Global.current_project.y_min - _pos.y - 1
|
||||
func set_pixel(image: Image, position: Vector2, color: Color) -> void:
|
||||
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 - position.y - 1
|
||||
|
||||
drawers[0].set_pixel(_sprite, _pos, _new_color)
|
||||
if h_mirror:
|
||||
drawers[1].set_pixel(_sprite, Vector2(mirror_x, _pos.y), _new_color)
|
||||
if v_mirror:
|
||||
drawers[2].set_pixel(_sprite, Vector2(mirror_x, mirror_y), _new_color)
|
||||
if v_mirror:
|
||||
drawers[3].set_pixel(_sprite, Vector2(_pos.x, mirror_y), _new_color)
|
||||
drawers[0].set_pixel(image, position, color, color_op)
|
||||
if horizontal_mirror:
|
||||
drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op)
|
||||
if vertical_mirror:
|
||||
drawers[2].set_pixel(image, Vector2(mirror_x, mirror_y), color, color_op)
|
||||
if vertical_mirror:
|
||||
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 selected_pixels := []
|
||||
var x_min := 0
|
||||
var x_max := 64
|
||||
var y_min := 0
|
||||
var y_max := 64
|
||||
|
||||
var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect
|
||||
|
||||
# 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_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("")
|
||||
|
||||
|
||||
func _set_selected_rect(value : Rect2) -> void:
|
||||
selected_rect = value
|
||||
Global.selection_rectangle.set_rect(value)
|
||||
|
||||
|
||||
func change_project() -> void:
|
||||
# Remove old nodes
|
||||
for container in Global.layers_container.get_children():
|
||||
|
@ -94,16 +100,7 @@ func change_project() -> void:
|
|||
self.animation_tags = animation_tags
|
||||
|
||||
# Change the selection rectangle
|
||||
if selected_pixels.size() != 0:
|
||||
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
|
||||
Global.selection_rectangle.set_rect(selected_rect)
|
||||
|
||||
# Change the guides
|
||||
for guide in Global.canvas.get_children():
|
||||
|
@ -114,11 +111,9 @@ func change_project() -> void:
|
|||
guide.visible = false
|
||||
|
||||
# Change the project brushes
|
||||
for child in Global.project_brush_container.get_children():
|
||||
child.queue_free()
|
||||
|
||||
Brushes.clear_project_brush()
|
||||
for brush in brushes:
|
||||
Global.create_brush_button(brush)
|
||||
Brushes.add_project_brush(brush)
|
||||
|
||||
var cameras = [Global.camera, Global.camera2, Global.camera_preview]
|
||||
var i := 0
|
||||
|
|
|
@ -19,9 +19,6 @@ func _ready() -> void:
|
|||
Import.import_brushes(Global.directory_module.get_brushes_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.get_ok().text = "Exit without saving"
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ func open(palette : String) -> void:
|
|||
self.popup_centered()
|
||||
Global.dialog_open(true)
|
||||
|
||||
left_color_button.modulate = Global.color_pickers[0].color
|
||||
right_color_button.modulate = Global.color_pickers[1].color
|
||||
left_color_button.modulate = Tools.get_assigned_color(BUTTON_LEFT)
|
||||
right_color_button.modulate = Tools.get_assigned_color(BUTTON_RIGHT)
|
||||
|
||||
|
||||
func _display_palette() -> void:
|
||||
|
@ -177,12 +177,12 @@ func _refresh_hint_tooltip(_index : int) -> 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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -220,11 +220,9 @@ func on_color_select(index : int) -> void:
|
|||
var color : Color = Global.palettes[current_palette].get_color(index)
|
||||
|
||||
if Input.is_action_just_pressed("left_mouse"):
|
||||
Global.color_pickers[0].color = color
|
||||
Global.update_custom_brush(0)
|
||||
Tools.assign_color(color, BUTTON_LEFT)
|
||||
elif Input.is_action_just_pressed("right_mouse"):
|
||||
Global.color_pickers[1].color = color
|
||||
Global.update_custom_brush(1)
|
||||
Tools.assign_color(color, BUTTON_RIGHT)
|
||||
|
||||
|
||||
func _load_palettes() -> void:
|
||||
|
|
|
@ -1,144 +1,156 @@
|
|||
extends Polygon2D
|
||||
|
||||
var img : Image
|
||||
var tex : ImageTexture
|
||||
var is_dragging := false
|
||||
var move_pixels := false
|
||||
var diff_x := 0.0
|
||||
var diff_y := 0.0
|
||||
var orig_x := 0.0
|
||||
var orig_y := 0.0
|
||||
var orig_colors := []
|
||||
|
||||
var _selected_rect := Rect2(0, 0, 0, 0)
|
||||
var _clipped_rect := Rect2(0, 0, 0, 0)
|
||||
var _move_image := Image.new()
|
||||
var _move_texture := ImageTexture.new()
|
||||
var _clear_image := Image.new()
|
||||
var _move_pixel := false
|
||||
var _clipboard := Image.new()
|
||||
var _undo_data := {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
img = Image.new()
|
||||
img.create(1, 1, false, Image.FORMAT_RGBA8)
|
||||
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")
|
||||
_clear_image.create(1, 1, false, Image.FORMAT_RGBA8)
|
||||
_clear_image.fill(Color(0, 0, 0, 0))
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if img.get_size() == polygon[2] - polygon[0]:
|
||||
draw_texture(tex, polygon[0], Color(1, 1, 1, 0.5))
|
||||
if _move_pixel:
|
||||
draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5))
|
||||
|
||||
|
||||
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
|
||||
func has_point(position : Vector2) -> bool:
|
||||
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
|
||||
|
||||
|
||||
export var brush_type := 0 # Global.Brush_Types.PIXEL
|
||||
export var custom_brush_index := -3
|
||||
var random_brushes := []
|
||||
var brush := Brushes.Brush.new()
|
||||
|
||||
|
||||
func _on_BrushButton_pressed() -> void:
|
||||
# Delete the brush on middle mouse press
|
||||
if Input.is_action_just_released("middle_mouse"):
|
||||
_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:
|
||||
Global.color_interpolation_containers[Global.brush_type_window_position].visible = false
|
||||
|
||||
Global.update_custom_brush(Global.brush_type_window_position)
|
||||
Global.brushes_popup.hide()
|
||||
Global.brushes_popup.select_brush(brush)
|
||||
|
||||
|
||||
func _on_DeleteButton_pressed() -> void:
|
||||
if brush_type != Global.Brush_Types.CUSTOM:
|
||||
if brush.type != Brushes.CUSTOM:
|
||||
return
|
||||
|
||||
if Global.custom_brush_indexes[0] == custom_brush_index:
|
||||
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()
|
||||
Global.brushes_popup.remove_brush(self)
|
||||
|
||||
|
||||
func _on_BrushButton_mouse_entered() -> void:
|
||||
if brush_type == Global.Brush_Types.CUSTOM:
|
||||
if brush.type == Brushes.CUSTOM:
|
||||
$DeleteButton.visible = true
|
||||
|
||||
|
||||
func _on_BrushButton_mouse_exited() -> void:
|
||||
if brush_type == Global.Brush_Types.CUSTOM:
|
||||
if brush.type == Brushes.CUSTOM:
|
||||
$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]
|
||||
|
||||
[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 )
|
||||
[ext_resource path="res://src/UI/BrushesPopup.gd" type="Script" id=1]
|
||||
|
||||
[node name="BrushesPopup" type="Popup"]
|
||||
margin_right = 226.0
|
||||
margin_bottom = 144.0
|
||||
rect_min_size = Vector2( 0, 144 )
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="TabContainer" type="TabContainer" parent="."]
|
||||
anchor_right = 1.0
|
||||
|
@ -55,36 +26,8 @@ size_flags_horizontal = 3
|
|||
scroll_horizontal_enabled = false
|
||||
|
||||
[node name="FileBrushContainer" type="GridContainer" parent="TabContainer/File"]
|
||||
margin_right = 104.0
|
||||
margin_bottom = 32.0
|
||||
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"]
|
||||
visible = false
|
||||
anchor_right = 1.0
|
||||
|
@ -99,7 +42,3 @@ scroll_horizontal_enabled = false
|
|||
|
||||
[node name="ProjectBrushContainer" type="GridContainer" parent="TabContainer/Project"]
|
||||
columns = 5
|
||||
|
||||
[editable path="TabContainer/File/FileBrushContainer/CircleBrushButton"]
|
||||
|
||||
[editable path="TabContainer/File/FileBrushContainer/FilledCircleBrushButton"]
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
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:
|
||||
var temp : Color = Global.color_pickers[0].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)
|
||||
Tools.swap_color()
|
||||
|
||||
|
||||
func _on_ColorPickerButton_color_changed(color : Color, right : bool):
|
||||
var mouse_button := int(right)
|
||||
# If the color changed while it's on full transparency, make it opaque (GH issue #54)
|
||||
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
|
||||
var button := BUTTON_RIGHT if right else BUTTON_LEFT
|
||||
Tools.assign_color(color, button)
|
||||
|
||||
|
||||
func _on_ColorPickerButton_pressed() -> void:
|
||||
|
@ -31,108 +29,11 @@ func _on_ColorPickerButton_popup_closed() -> void:
|
|||
|
||||
|
||||
func _on_ColorDefaults_pressed() -> void:
|
||||
Global.color_pickers[0].color = Color.black
|
||||
Global.color_pickers[1].color = Color.white
|
||||
Global.update_custom_brush(0)
|
||||
Global.update_custom_brush(1)
|
||||
Tools.default_color()
|
||||
|
||||
|
||||
func _on_FitToFrameButton_pressed() -> void:
|
||||
Global.camera.fit_to_frame(Global.current_project.size)
|
||||
|
||||
|
||||
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
|
||||
func update_color(color : Color, button : int) -> void:
|
||||
if button == BUTTON_LEFT:
|
||||
left_picker.color = color
|
||||
else:
|
||||
Global.fill_pattern_containers[mouse_button].visible = false
|
||||
|
||||
|
||||
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
|
||||
right_picker.color = color
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,22 +1,8 @@
|
|||
extends TextureButton
|
||||
|
||||
|
||||
var image : Image
|
||||
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)
|
||||
var pattern := Patterns.Pattern.new()
|
||||
|
||||
|
||||
func _on_PatternButton_pressed() -> void:
|
||||
Global.pattern_images[Global.pattern_window_position] = image
|
||||
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()
|
||||
Global.patterns_popup.select_pattern(pattern)
|
||||
|
|
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"]
|
||||
margin_right = 226.0
|
||||
margin_bottom = 104.0
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
|
|
@ -1,98 +1,40 @@
|
|||
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:
|
||||
# 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:
|
||||
t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]])
|
||||
|
||||
Global.update_hint_tooltips()
|
||||
|
||||
|
||||
func _input(event : InputEvent) -> void:
|
||||
if Global.has_focus:
|
||||
if event.is_action_pressed("undo") or event.is_action_pressed("redo") or event.is_action_pressed("redo_secondary"):
|
||||
if not Global.has_focus:
|
||||
return
|
||||
for action in ["undo", "redo", "redo_secondary"]:
|
||||
if event.is_action_pressed(action):
|
||||
return
|
||||
for t in tools: # Handle tool shortcuts
|
||||
if event.is_action_pressed(t[2]): # Shortcut for right button (with Alt)
|
||||
_on_Tool_pressed(t[0], false, false)
|
||||
elif event.is_action_pressed(t[1]): # Shortcut for left button
|
||||
_on_Tool_pressed(t[0], false, true)
|
||||
for t in tools: # Handle tool shortcuts
|
||||
if event.is_action_pressed("right_" + t[1] + "_tool"): # Shortcut for right button (with Alt)
|
||||
Tools.assign_tool(t[0].name, BUTTON_RIGHT)
|
||||
elif event.is_action_pressed("left_" + t[1] + "_tool"): # Shortcut for left button
|
||||
Tools.assign_tool(t[0].name, BUTTON_LEFT)
|
||||
|
||||
|
||||
func _on_Tool_pressed(tool_pressed : BaseButton, mouse_press := true, key_for_left := true) -> void:
|
||||
var current_action := tool_pressed.name
|
||||
var current_tool : int = Global.Tools.keys().find(current_action.to_upper())
|
||||
var left_tool_name := str(Global.Tools.keys()[Global.current_tools[0]]).to_lower()
|
||||
var right_tool_name := str(Global.Tools.keys()[Global.current_tools[1]]).to_lower()
|
||||
var current_mouse_button := -1
|
||||
|
||||
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)
|
||||
func _on_Tool_pressed(tool_pressed : BaseButton) -> void:
|
||||
var button := -1
|
||||
button = BUTTON_LEFT if Input.is_action_just_released("left_mouse") else button
|
||||
button = BUTTON_RIGHT if Input.is_action_just_released("right_mouse") else button
|
||||
if button != -1:
|
||||
Tools.assign_tool(tool_pressed.name, button)
|
||||
|
|
|
@ -39,6 +39,9 @@ func setup_edit_menu() -> void:
|
|||
var edit_menu_items := {
|
||||
"Undo" : InputMap.get_action_list("undo")[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,
|
||||
"Preferences" : 0
|
||||
}
|
||||
|
@ -201,15 +204,16 @@ func edit_menu_id_pressed(id : int) -> void:
|
|||
Global.control.redone = true
|
||||
Global.current_project.undo_redo.redo()
|
||||
Global.control.redone = false
|
||||
2: # Clear selection
|
||||
Global.canvas.handle_undo("Rectangle Select")
|
||||
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
|
||||
Global.current_project.selected_pixels.clear()
|
||||
Global.canvas.handle_redo("Rectangle Select")
|
||||
3: # Preferences
|
||||
2: # Copy
|
||||
Global.selection_rectangle.copy()
|
||||
3: # paste
|
||||
Global.selection_rectangle.paste()
|
||||
4: # Delete
|
||||
Global.selection_rectangle.delete()
|
||||
5: # Clear selection
|
||||
Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0))
|
||||
Global.selection_rectangle.select_rect()
|
||||
6: # Preferences
|
||||
Global.preferences_dialog.popup_centered(Vector2(400, 280))
|
||||
Global.dialog_open(true)
|
||||
|
||||
|
|
Loading…
Reference in a new issue