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:
Kinwailo 2020-07-09 20:22:17 +08:00 committed by GitHub
parent e1724148fc
commit 4a668f71f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 2489 additions and 2389 deletions

View file

@ -97,6 +97,15 @@ msgstr ""
msgid "Redo" msgid "Redo"
msgstr "" msgstr ""
msgid "Copy"
msgstr ""
msgid "Paste"
msgstr ""
msgid "Delete"
msgstr ""
msgid "Scale Image" msgid "Scale Image"
msgstr "" msgstr ""

View file

@ -14,6 +14,11 @@ _global_script_classes=[ {
"language": "GDScript", "language": "GDScript",
"path": "res://src/Classes/AnimationTag.gd" "path": "res://src/Classes/AnimationTag.gd"
}, { }, {
"base": "Popup",
"class": "Brushes",
"language": "GDScript",
"path": "res://src/UI/BrushesPopup.gd"
}, {
"base": "Node2D", "base": "Node2D",
"class": "Canvas", "class": "Canvas",
"language": "GDScript", "language": "GDScript",
@ -59,6 +64,11 @@ _global_script_classes=[ {
"language": "GDScript", "language": "GDScript",
"path": "res://src/Palette/PaletteColor.gd" "path": "res://src/Palette/PaletteColor.gd"
}, { }, {
"base": "PopupPanel",
"class": "Patterns",
"language": "GDScript",
"path": "res://src/UI/PatternsPopup.gd"
}, {
"base": "Reference", "base": "Reference",
"class": "Project", "class": "Project",
"language": "GDScript", "language": "GDScript",
@ -66,6 +76,7 @@ _global_script_classes=[ {
} ] } ]
_global_script_class_icons={ _global_script_class_icons={
"AnimationTag": "", "AnimationTag": "",
"Brushes": "",
"Canvas": "", "Canvas": "",
"Cel": "", "Cel": "",
"Drawer": "", "Drawer": "",
@ -75,6 +86,7 @@ _global_script_class_icons={
"LayerButton": "", "LayerButton": "",
"Palette": "", "Palette": "",
"PaletteColor": "", "PaletteColor": "",
"Patterns": "",
"Project": "" "Project": ""
} }
@ -97,6 +109,7 @@ Global="*res://src/Autoload/Global.gd"
Import="*res://src/Autoload/Import.gd" Import="*res://src/Autoload/Import.gd"
OpenSave="*res://src/Autoload/OpenSave.gd" OpenSave="*res://src/Autoload/OpenSave.gd"
DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd" DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd"
Tools="*res://src/Autoload/Tools.gd"
Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd" Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd"
[debug] [debug]

View file

@ -1,336 +1,6 @@
extends Node extends Node
var drawer := Drawer.new()
var mouse_press_pixels := [] # Cleared after mouse release
var mouse_press_pressure_values := [] # Cleared after mouse release
func reset() -> void:
drawer.reset()
mouse_press_pixels.clear()
mouse_press_pressure_values.clear()
func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color, pen_pressure : float, current_mouse_button := -1, current_action := -1) -> void:
var x_min = Global.current_project.x_min
var x_max = Global.current_project.x_max
var y_min = Global.current_project.y_min
var y_max = Global.current_project.y_max
# Check if Tiling is enabled and whether mouse is in TilingPreviews
if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
pos = pos.posmodv(Global.current_project.size)
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
return
var pos_floored := pos.floor()
var current_pixel_color = sprite.get_pixelv(pos)
var saved_pixel_index := mouse_press_pixels.find(pos_floored)
if current_action == Global.Tools.PENCIL && color.a < 1:
color = blend_colors(color, current_pixel_color)
if current_pixel_color != color && (saved_pixel_index == -1 || pen_pressure > mouse_press_pressure_values[saved_pixel_index]):
if current_action == Global.Tools.LIGHTENDARKEN:
var ld : int = Global.ld_modes[current_mouse_button]
var ld_amount : float = Global.ld_amounts[current_mouse_button]
if ld == Global.Lighten_Darken_Mode.LIGHTEN:
color = current_pixel_color.lightened(ld_amount)
else:
color = current_pixel_color.darkened(ld_amount)
if saved_pixel_index == -1:
mouse_press_pixels.append(pos_floored)
mouse_press_pressure_values.append(pen_pressure)
else:
mouse_press_pressure_values[saved_pixel_index] = pen_pressure
drawer.set_pixel(sprite, pos, color)
func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_button : int, pen_pressure : float, current_action := -1) -> void:
if Global.can_draw && Global.has_focus:
var x_min = Global.current_project.x_min
var x_max = Global.current_project.x_max
var y_min = Global.current_project.y_min
var y_max = Global.current_project.y_max
if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.ALPHA:
if current_action == Global.Tools.PENCIL:
color.a *= pen_pressure
elif current_action == Global.Tools.ERASER: # This is not working
color.a *= (1.0 - pen_pressure)
var brush_size : int = Global.brush_sizes[current_mouse_button]
var brush_type : int = Global.current_brush_types[current_mouse_button]
var horizontal_mirror : bool = Global.horizontal_mirror[current_mouse_button]
var vertical_mirror : bool = Global.vertical_mirror[current_mouse_button]
var pixel_perfect : bool = Global.pixel_perfect[current_mouse_button]
drawer.pixel_perfect = pixel_perfect if brush_size == 1 else false
drawer.h_mirror = horizontal_mirror
drawer.v_mirror = vertical_mirror
if brush_type == Global.Brush_Types.PIXEL || current_action == Global.Tools.LIGHTENDARKEN:
var start_pos_x = floor(pos.x - (brush_size >> 1))
var start_pos_y = floor(pos.y - (brush_size >> 1))
var end_pos_x = floor(start_pos_x + brush_size)
var end_pos_y = floor(start_pos_y + brush_size)
for cur_pos_x in range(start_pos_x, end_pos_x):
for cur_pos_y in range(start_pos_y, end_pos_y):
draw_pixel_blended(sprite, Vector2(cur_pos_x, cur_pos_y), color, pen_pressure, current_mouse_button, current_action)
Global.canvas.sprite_changed_this_frame = true
elif brush_type == Global.Brush_Types.CIRCLE || brush_type == Global.Brush_Types.FILLED_CIRCLE:
plot_circle(sprite, pos.x, pos.y, brush_size, color, pen_pressure, brush_type == Global.Brush_Types.FILLED_CIRCLE)
Global.canvas.sprite_changed_this_frame = true
else:
var brush_index : int = Global.custom_brush_indexes[current_mouse_button]
var custom_brush_image : Image
if brush_type != Global.Brush_Types.RANDOM_FILE:
custom_brush_image = Global.brush_images[current_mouse_button]
else: # Handle random brush
var brush_button = Global.file_brush_container.get_child(brush_index + 3)
var random_index = randi() % brush_button.random_brushes.size()
custom_brush_image = Image.new()
custom_brush_image.copy_from(brush_button.random_brushes[random_index])
var custom_brush_size = custom_brush_image.get_size()
custom_brush_image.resize(custom_brush_size.x * brush_size, custom_brush_size.y * brush_size, Image.INTERPOLATE_NEAREST)
custom_brush_image = Global.blend_image_with_color(custom_brush_image, color, Global.interpolate_spinboxes[current_mouse_button].value / 100)
custom_brush_image.lock()
var custom_brush_size := custom_brush_image.get_size() - Vector2.ONE
pos = pos.floor()
# #Check if Tiling is enabled and whether mouse is in TilingPreviews
if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
pos = pos.posmodv(Global.current_project.size)
var dst := rectangle_center(pos, custom_brush_size)
var src_rect := Rect2(Vector2.ZERO, custom_brush_size + Vector2.ONE)
# Rectangle with the same size as the brush, but at cursor's position
var pos_rect := Rect2(dst, custom_brush_size + Vector2.ONE)
# The selection rectangle
# If there's no rectangle, the whole canvas is considered a selection
var selection_rect := Rect2()
selection_rect.position = Vector2(x_min, y_min)
selection_rect.end = Vector2(x_max, y_max)
# Intersection of the position rectangle and selection
var pos_rect_clipped := pos_rect.clip(selection_rect)
# If the size is 0, that means that the brush wasn't positioned inside the selection
if pos_rect_clipped.size == Vector2.ZERO:
return
# Re-position src_rect and dst based on the clipped position
var pos_difference := (pos_rect.position - pos_rect_clipped.position).abs()
# Obviously, if pos_rect and pos_rect_clipped are the same, pos_difference is Vector2.ZERO
src_rect.position = pos_difference
dst += pos_difference
src_rect.end -= pos_rect.end - pos_rect_clipped.end
# If the selection rectangle is smaller than the brush, ...
# ... make sure pixels aren't being drawn outside the selection by adjusting src_rect's size
src_rect.size.x = min(src_rect.size.x, selection_rect.size.x)
src_rect.size.y = min(src_rect.size.y, selection_rect.size.y)
# Handle mirroring
var mirror_x = x_max + x_min - pos.x - (pos.x - dst.x)
var mirror_y = y_max + y_min - pos.y - (pos.y - dst.y)
if int(pos_rect_clipped.size.x) % 2 != 0:
mirror_x -= 1
if int(pos_rect_clipped.size.y) % 2 != 0:
mirror_y -= 1
# Use custom blend function cause of godot's issue #31124
if color.a > 0: # If it's the pencil
sprite.blend_rect(custom_brush_image, src_rect, dst)
if horizontal_mirror:
sprite.blend_rect(custom_brush_image, src_rect, Vector2(mirror_x, dst.y))
if vertical_mirror:
sprite.blend_rect(custom_brush_image, src_rect, Vector2(dst.x, mirror_y))
if horizontal_mirror && vertical_mirror:
sprite.blend_rect(custom_brush_image, src_rect, Vector2(mirror_x, mirror_y))
else: # if it's transparent - if it's the eraser
var custom_brush := Image.new()
if brush_type == Global.Brush_Types.CUSTOM:
custom_brush.copy_from(Global.current_project.brushes[brush_index])
else:
custom_brush.copy_from(Global.file_brushes[brush_index])
custom_brush_size = custom_brush.get_size()
custom_brush.resize(custom_brush_size.x * brush_size, custom_brush_size.y * brush_size, Image.INTERPOLATE_NEAREST)
var custom_brush_blended = Global.blend_image_with_color(custom_brush, color, 1)
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, dst)
if horizontal_mirror:
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(mirror_x, dst.y))
if vertical_mirror:
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(dst.x, mirror_y))
if horizontal_mirror && vertical_mirror:
sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(mirror_x, mirror_y))
sprite.lock()
Global.canvas.sprite_changed_this_frame = true
Global.canvas.previous_mouse_pos_for_lines = pos.floor() + Vector2(0.5, 0.5)
Global.canvas.previous_mouse_pos_for_lines.x = clamp(Global.canvas.previous_mouse_pos_for_lines.x, Global.canvas.location.x, Global.canvas.location.x + Global.current_project.size.x)
Global.canvas.previous_mouse_pos_for_lines.y = clamp(Global.canvas.previous_mouse_pos_for_lines.y, Global.canvas.location.y, Global.canvas.location.y + Global.current_project.size.y)
if Global.canvas.is_making_line:
Global.canvas.line_pos[0] = Global.canvas.previous_mouse_pos_for_lines
# Bresenham's Algorithm
# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
func fill_gaps(sprite : Image, end_pos : Vector2, start_pos : Vector2, color : Color, current_mouse_button : int, pen_pressure : float, current_action := -1) -> void:
var previous_mouse_pos_floored = start_pos.floor()
var mouse_pos_floored = end_pos.floor()
var dx := int(abs(mouse_pos_floored.x - previous_mouse_pos_floored.x))
var dy := int(-abs(mouse_pos_floored.y - previous_mouse_pos_floored.y))
var err := dx + dy
var e2 := err << 1 # err * 2
var sx = 1 if previous_mouse_pos_floored.x < mouse_pos_floored.x else -1
var sy = 1 if previous_mouse_pos_floored.y < mouse_pos_floored.y else -1
var x = previous_mouse_pos_floored.x
var y = previous_mouse_pos_floored.y
while !(x == mouse_pos_floored.x && y == mouse_pos_floored.y):
draw_brush(sprite, Vector2(x, y), color, current_mouse_button, pen_pressure, current_action)
e2 = err << 1
if e2 >= dy:
err += dy
x += sx
if e2 <= dx:
err += dx
y += sy
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, pen_pressure : float, fill := false) -> void:
var radius := r # Used later for filling
var x := -r
var y := 0
var err := 2 - r * 2 # II. Quadrant
while x < 0:
var quadrant_1 := Vector2(xm - x, ym + y)
var quadrant_2 := Vector2(xm - y, ym - x)
var quadrant_3 := Vector2(xm + x, ym - y)
var quadrant_4 := Vector2(xm + y, ym + x)
draw_pixel_blended(sprite, quadrant_1, color, pen_pressure)
draw_pixel_blended(sprite, quadrant_2, color, pen_pressure)
draw_pixel_blended(sprite, quadrant_3, color, pen_pressure)
draw_pixel_blended(sprite, quadrant_4, color, pen_pressure)
r = err
if r <= y:
y += 1
err += y * 2 + 1
if r > x || err > y:
x += 1
err += x * 2 + 1
if fill:
for j in range (-radius, radius + 1):
for i in range (-radius, radius + 1):
if i * i + j * j <= radius * radius:
var draw_pos := Vector2(i + xm, j + ym)
draw_pixel_blended(sprite, draw_pos, color, Global.canvas.pen_pressure)
# Thanks to https://en.wikipedia.org/wiki/Flood_fill
func flood_fill(sprite : Image, pos : Vector2, target_color : Color, replace_color : Color) -> void:
var x_min = Global.current_project.x_min
var x_max = Global.current_project.x_max
var y_min = Global.current_project.y_min
var y_max = Global.current_project.y_max
pos = pos.floor()
var pixel = sprite.get_pixelv(pos)
if target_color == replace_color:
return
elif pixel != target_color:
return
else:
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
return
var q = [pos]
for n in q:
# If the difference in colors is very small, break the loop (thanks @azagaya on GitHub!)
if target_color == replace_color:
break
var west : Vector2 = n
var east : Vector2 = n
while west.x >= x_min && sprite.get_pixelv(west) == target_color:
west += Vector2.LEFT
while east.x < x_max && sprite.get_pixelv(east) == target_color:
east += Vector2.RIGHT
for px in range(west.x + 1, east.x):
var p := Vector2(px, n.y)
# Draw
sprite.set_pixelv(p, replace_color)
replace_color = sprite.get_pixelv(p)
var north := p + Vector2.UP
var south := p + Vector2.DOWN
if north.y >= y_min && sprite.get_pixelv(north) == target_color:
q.append(north)
if south.y < y_max && sprite.get_pixelv(south) == target_color:
q.append(south)
Global.canvas.sprite_changed_this_frame = true
func pattern_fill(sprite : Image, pos : Vector2, pattern : Image, target_color : Color, var offset : Vector2) -> void:
var x_min = Global.current_project.x_min
var x_max = Global.current_project.x_max
var y_min = Global.current_project.y_min
var y_max = Global.current_project.y_max
pos = pos.floor()
if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)):
return
pattern.lock()
var pattern_size := pattern.get_size()
var q = [pos]
for n in q:
var west : Vector2 = n
var east : Vector2 = n
while west.x >= x_min && sprite.get_pixelv(west) == target_color:
west += Vector2.LEFT
while east.x < x_max && sprite.get_pixelv(east) == target_color:
east += Vector2.RIGHT
for px in range(west.x + 1, east.x):
var p := Vector2(px, n.y)
var xx : int = int(px + offset.x) % int(pattern_size.x)
var yy : int = int(n.y + offset.y) % int(pattern_size.y)
var pattern_color : Color = pattern.get_pixel(xx, yy)
if pattern_color == target_color:
continue
sprite.set_pixelv(p, pattern_color)
var north := p + Vector2.UP
var south := p + Vector2.DOWN
if north.y >= y_min && sprite.get_pixelv(north) == target_color:
q.append(north)
if south.y < y_max && sprite.get_pixelv(south) == target_color:
q.append(south)
pattern.unlock()
Global.canvas.sprite_changed_this_frame = true
func blend_colors(color_1 : Color, color_2 : Color) -> Color:
var color := Color()
color.a = color_1.a + color_2.a * (1 - color_1.a) # Blend alpha
if color.a != 0:
# Blend colors
color.r = (color_1.r * color_1.a + color_2.r * color_2.a * (1-color_1.a)) / color.a
color.g = (color_1.g * color_1.a + color_2.g * color_2.a * (1-color_1.a)) / color.a
color.b = (color_1.b * color_1.a + color_2.b * color_2.a * (1-color_1.a)) / color.a
return color
func scale3X(sprite : Image, tol : float = 50) -> Image: func scale3X(sprite : Image, tol : float = 50) -> Image:
var scaled = Image.new() var scaled = Image.new()
scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8)
@ -829,13 +499,3 @@ func adjust_hsv(img: Image, id : int, delta : float) -> void:
img.set_pixel(i,j,c) img.set_pixel(i,j,c)
img.unlock() img.unlock()
# Checks if a point is inside a rectangle
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y
# Returns the position in the middle of a rectangle
func rectangle_center(rect_position : Vector2, rect_size : Vector2) -> Vector2:
return (rect_position - rect_size / 2).floor()

View file

@ -3,15 +3,8 @@ extends Node
enum Grid_Types {CARTESIAN, ISOMETRIC, ALL} enum Grid_Types {CARTESIAN, ISOMETRIC, ALL}
enum Pressure_Sensitivity {NONE, ALPHA, SIZE, ALPHA_AND_SIZE} enum Pressure_Sensitivity {NONE, ALPHA, SIZE, ALPHA_AND_SIZE}
enum Brush_Types {PIXEL, CIRCLE, FILLED_CIRCLE, FILE, RANDOM_FILE, CUSTOM}
enum Direction {UP, DOWN, LEFT, RIGHT} enum Direction {UP, DOWN, LEFT, RIGHT}
enum Mouse_Button {LEFT, RIGHT}
enum Tools {PENCIL, ERASER, BUCKET, LIGHTENDARKEN, RECTSELECT, COLORPICKER, ZOOM}
enum Theme_Types {DARK, BLUE, CARAMEL, LIGHT} enum Theme_Types {DARK, BLUE, CARAMEL, LIGHT}
enum Fill_Area {SAME_COLOR_AREA, SAME_COLOR_PIXELS}
enum Fill_With {COLOR, PATTERN}
enum Lighten_Darken_Mode {LIGHTEN, DARKEN}
enum Zoom_Mode {ZOOM_IN, ZOOM_OUT}
# Stuff for arrowkey-based canvas movements nyaa ^.^ # Stuff for arrowkey-based canvas movements nyaa ^.^
const low_speed_move_rate := 150.0 const low_speed_move_rate := 150.0
@ -44,8 +37,8 @@ var pressure_sensitivity_mode = Pressure_Sensitivity.NONE
var open_last_project := false var open_last_project := false
var smooth_zoom := true var smooth_zoom := true
var cursor_image = preload("res://assets/graphics/cursor_icons/cursor.png") var cursor_image = preload("res://assets/graphics/cursor_icons/cursor.png")
var left_cursor_tool_texture : ImageTexture var left_cursor_tool_texture := ImageTexture.new()
var right_cursor_tool_texture : ImageTexture var right_cursor_tool_texture := ImageTexture.new()
var image_clipboard : Image var image_clipboard : Image
var play_only_tags := true var play_only_tags := true
@ -68,27 +61,11 @@ var autosave_interval := 5.0
var enable_autosave := true var enable_autosave := true
# Tools & options # Tools & options
var current_tools := [Tools.PENCIL, Tools.ERASER]
var show_left_tool_icon := true var show_left_tool_icon := true
var show_right_tool_icon := true var show_right_tool_icon := true
var left_square_indicator_visible := true var left_square_indicator_visible := true
var right_square_indicator_visible := false var right_square_indicator_visible := false
var fill_areas := [Fill_Area.SAME_COLOR_AREA, Fill_Area.SAME_COLOR_AREA]
var fill_with := [Fill_With.COLOR, Fill_With.COLOR]
var fill_pattern_offsets := [Vector2.ZERO, Vector2.ZERO]
var ld_modes := [Lighten_Darken_Mode.LIGHTEN, Lighten_Darken_Mode.LIGHTEN]
var ld_amounts := [0.1, 0.1]
var color_picker_for := [Mouse_Button.LEFT, Mouse_Button.RIGHT]
var zoom_modes := [Zoom_Mode.ZOOM_IN, Zoom_Mode.ZOOM_OUT]
var horizontal_mirror := [false, false]
var vertical_mirror := [false, false]
var pixel_perfect := [false, false]
# View menu options # View menu options
var tile_mode := false var tile_mode := false
var draw_grid := false var draw_grid := false
@ -102,25 +79,6 @@ var onion_skinning_past_rate := 1.0
var onion_skinning_future_rate := 1.0 var onion_skinning_future_rate := 1.0
var onion_skinning_blue_red := false var onion_skinning_blue_red := false
# Brushes
var file_brushes := []
var brush_sizes := [1, 1]
var current_brush_types := [Brush_Types.PIXEL, Brush_Types.PIXEL]
var brush_images := [Image.new(), Image.new()]
var brush_textures := [ImageTexture.new(), ImageTexture.new()]
var brush_type_window_position : int = Mouse_Button.LEFT
var left_circle_points := []
var right_circle_points := []
var brushes_from_files := 0
var custom_brush_indexes := [-1, -1]
# Patterns
var patterns := []
var pattern_window_position : int = Mouse_Button.LEFT
var pattern_images := [Image.new(), Image.new()]
# Palettes # Palettes
var palettes := {} var palettes := {}
@ -158,41 +116,11 @@ var export_dialog : AcceptDialog
var preferences_dialog : AcceptDialog var preferences_dialog : AcceptDialog
var unsaved_changes_dialog : ConfirmationDialog var unsaved_changes_dialog : ConfirmationDialog
var color_pickers := []
var color_switch_button : BaseButton var color_switch_button : BaseButton
var tool_options_containers := []
var brush_type_containers := []
var brush_type_buttons := []
var brushes_popup : Popup var brushes_popup : Popup
var file_brush_container : GridContainer
var project_brush_container : GridContainer
var patterns_popup : Popup var patterns_popup : Popup
var brush_size_edits := []
var brush_size_sliders := []
var pixel_perfect_containers := []
var color_interpolation_containers := []
var interpolate_spinboxes := []
var interpolate_sliders := []
var fill_area_containers := []
var fill_pattern_containers := []
var ld_containers := []
var ld_amount_sliders := []
var ld_amount_spinboxes := []
var colorpicker_containers := []
var zoom_containers := []
var mirror_containers := []
var animation_timeline : Panel var animation_timeline : Panel
var animation_timer : Timer var animation_timer : Timer
@ -251,21 +179,6 @@ func _ready() -> void:
right_cursor = find_node_by_name(root, "RightCursor") right_cursor = find_node_by_name(root, "RightCursor")
canvas = find_node_by_name(root, "Canvas") canvas = find_node_by_name(root, "Canvas")
var pencil_cursor_image = preload("res://assets/graphics/cursor_icons/pencil_cursor.png")
var eraser_cursor_image = preload("res://assets/graphics/cursor_icons/eraser_cursor.png")
left_cursor_tool_texture = ImageTexture.new()
if pencil_cursor_image is Image:
left_cursor_tool_texture.create_from_image(pencil_cursor_image)
elif pencil_cursor_image is ImageTexture:
left_cursor_tool_texture.create_from_image(pencil_cursor_image.get_data())
right_cursor_tool_texture = ImageTexture.new()
if eraser_cursor_image is Image:
right_cursor_tool_texture.create_from_image(eraser_cursor_image)
elif eraser_cursor_image is ImageTexture:
right_cursor_tool_texture.create_from_image(eraser_cursor_image.get_data())
tabs = find_node_by_name(root, "Tabs") tabs = find_node_by_name(root, "Tabs")
main_viewport = find_node_by_name(root, "ViewportContainer") main_viewport = find_node_by_name(root, "ViewportContainer")
second_viewport = find_node_by_name(root, "ViewportContainer2") second_viewport = find_node_by_name(root, "ViewportContainer2")
@ -294,58 +207,11 @@ func _ready() -> void:
preferences_dialog = find_node_by_name(root, "PreferencesDialog") preferences_dialog = find_node_by_name(root, "PreferencesDialog")
unsaved_changes_dialog = find_node_by_name(root, "UnsavedCanvasDialog") unsaved_changes_dialog = find_node_by_name(root, "UnsavedCanvasDialog")
tool_options_containers.append(find_node_by_name(root, "LeftToolOptions"))
tool_options_containers.append(find_node_by_name(root, "RightToolOptions"))
color_pickers.append(find_node_by_name(root, "LeftColorPickerButton"))
color_pickers.append(find_node_by_name(root, "RightColorPickerButton"))
color_switch_button = find_node_by_name(root, "ColorSwitch") color_switch_button = find_node_by_name(root, "ColorSwitch")
brush_type_containers.append(find_node_by_name(tool_options_containers[0], "LeftBrushType"))
brush_type_containers.append(find_node_by_name(tool_options_containers[1], "RightBrushType"))
brush_type_buttons.append(find_node_by_name(brush_type_containers[0], "LeftBrushTypeButton"))
brush_type_buttons.append(find_node_by_name(brush_type_containers[1], "RightBrushTypeButton"))
brushes_popup = find_node_by_name(root, "BrushesPopup") brushes_popup = find_node_by_name(root, "BrushesPopup")
file_brush_container = find_node_by_name(brushes_popup, "FileBrushContainer")
project_brush_container = find_node_by_name(brushes_popup, "ProjectBrushContainer")
patterns_popup = find_node_by_name(root, "PatternsPopup") patterns_popup = find_node_by_name(root, "PatternsPopup")
brush_size_edits.append(find_node_by_name(root, "LeftBrushSizeEdit"))
brush_size_sliders.append(find_node_by_name(root, "LeftBrushSizeSlider"))
brush_size_edits.append(find_node_by_name(root, "RightBrushSizeEdit"))
brush_size_sliders.append(find_node_by_name(root, "RightBrushSizeSlider"))
pixel_perfect_containers.append(find_node_by_name(root, "LeftBrushPixelPerfectMode"))
pixel_perfect_containers.append(find_node_by_name(root, "RightBrushPixelPerfectMode"))
color_interpolation_containers.append(find_node_by_name(root, "LeftColorInterpolation"))
color_interpolation_containers.append(find_node_by_name(root, "RightColorInterpolation"))
interpolate_spinboxes.append(find_node_by_name(root, "LeftInterpolateFactor"))
interpolate_sliders.append(find_node_by_name(root, "LeftInterpolateSlider"))
interpolate_spinboxes.append(find_node_by_name(root, "RightInterpolateFactor"))
interpolate_sliders.append(find_node_by_name(root, "RightInterpolateSlider"))
fill_area_containers.append(find_node_by_name(root, "LeftFillArea"))
fill_pattern_containers.append(find_node_by_name(root, "LeftFillPattern"))
fill_area_containers.append(find_node_by_name(root, "RightFillArea"))
fill_pattern_containers.append(find_node_by_name(root, "RightFillPattern"))
ld_containers.append(find_node_by_name(root, "LeftLDOptions"))
ld_amount_sliders.append(find_node_by_name(root, "LeftLDAmountSlider"))
ld_amount_spinboxes.append(find_node_by_name(root, "LeftLDAmountSpinbox"))
ld_containers.append(find_node_by_name(root, "RightLDOptions"))
ld_amount_sliders.append(find_node_by_name(root, "RightLDAmountSlider"))
ld_amount_spinboxes.append(find_node_by_name(root, "RightLDAmountSpinbox"))
colorpicker_containers.append(find_node_by_name(root, "LeftColorPickerOptions"))
colorpicker_containers.append(find_node_by_name(root, "RightColorPickerOptions"))
zoom_containers.append(find_node_by_name(root, "LeftZoomOptions"))
zoom_containers.append(find_node_by_name(root, "RightZoomOptions"))
mirror_containers.append(find_node_by_name(root, "LeftMirrorButtons"))
mirror_containers.append(find_node_by_name(root, "RightMirrorButtons"))
animation_timeline = find_node_by_name(root, "AnimationTimeline") animation_timeline = find_node_by_name(root, "AnimationTimeline")
layers_container = find_node_by_name(animation_timeline, "LayersContainer") layers_container = find_node_by_name(animation_timeline, "LayersContainer")
@ -605,143 +471,6 @@ Hold %s to make a line""") % [InputMap.get_action_list("left_eraser_tool")[0].as
(%s)""") % InputMap.get_action_list("go_to_last_frame")[0].as_text() (%s)""") % InputMap.get_action_list("go_to_last_frame")[0].as_text()
func create_brush_button(brush_img : Image, brush_type := Brush_Types.CUSTOM, hint_tooltip := "") -> void:
var brush_container
var brush_button = load("res://src/UI/BrushButton.tscn").instance()
brush_button.brush_type = brush_type
if brush_type == Brush_Types.FILE || brush_type == Brush_Types.RANDOM_FILE:
brush_button.custom_brush_index = file_brushes.size() - 1
brush_container = file_brush_container
else:
brush_button.custom_brush_index = current_project.brushes.size() - 1
brush_container = project_brush_container
var brush_tex := ImageTexture.new()
brush_tex.create_from_image(brush_img, 0)
brush_button.get_child(0).texture = brush_tex
brush_button.hint_tooltip = hint_tooltip
brush_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
if brush_type == Brush_Types.RANDOM_FILE:
brush_button.random_brushes.append(brush_img)
brush_container.add_child(brush_button)
func create_pattern_button(image : Image, hint_tooltip := "") -> void:
var pattern_button : BaseButton = load("res://src/UI/PatternButton.tscn").instance()
pattern_button.image = image
var pattern_tex := ImageTexture.new()
pattern_tex.create_from_image(image, 0)
pattern_button.get_child(0).texture = pattern_tex
pattern_button.hint_tooltip = hint_tooltip
pattern_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
patterns_popup.get_node("ScrollContainer/PatternContainer").add_child(pattern_button)
func remove_brush_buttons() -> void:
current_brush_types[0] = Brush_Types.PIXEL
current_brush_types[1] = Brush_Types.PIXEL
for child in project_brush_container.get_children():
child.queue_free()
func undo_custom_brush(_brush_button : BaseButton = null) -> void:
general_undo()
var action_name : String = current_project.undo_redo.get_current_action_name()
if action_name == "Delete Custom Brush":
project_brush_container.add_child(_brush_button)
project_brush_container.move_child(_brush_button, _brush_button.custom_brush_index)
_brush_button.get_node("DeleteButton").visible = false
func redo_custom_brush(_brush_button : BaseButton = null) -> void:
general_redo()
var action_name : String = current_project.undo_redo.get_current_action_name()
if action_name == "Delete Custom Brush":
project_brush_container.remove_child(_brush_button)
func update_custom_brush(mouse_button : int) -> void:
var brush_type : int = current_brush_types[mouse_button]
if brush_type == Brush_Types.PIXEL:
var pixel = preload("res://assets/graphics/pixel_image.png")
if pixel is Image:
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0)
elif pixel is ImageTexture:
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0)
elif brush_type == Brush_Types.CIRCLE:
var pixel = preload("res://assets/graphics/circle_9x9.png")
if pixel is Image:
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0)
elif pixel is ImageTexture:
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0)
left_circle_points = plot_circle(brush_sizes[0])
right_circle_points = plot_circle(brush_sizes[1])
elif brush_type == Brush_Types.FILLED_CIRCLE:
var pixel = preload("res://assets/graphics/circle_filled_9x9.png")
if pixel is Image:
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0)
elif pixel is ImageTexture:
brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0)
left_circle_points = plot_circle(brush_sizes[0])
right_circle_points = plot_circle(brush_sizes[1])
else:
var custom_brush := Image.new()
if brush_type == Brush_Types.FILE or brush_type == Brush_Types.RANDOM_FILE:
custom_brush.copy_from(file_brushes[custom_brush_indexes[mouse_button]])
else:
custom_brush.copy_from(current_project.brushes[custom_brush_indexes[mouse_button]])
var custom_brush_size = custom_brush.get_size()
custom_brush.resize(custom_brush_size.x * brush_sizes[mouse_button], custom_brush_size.y * brush_sizes[mouse_button], Image.INTERPOLATE_NEAREST)
brush_images[mouse_button] = blend_image_with_color(custom_brush, color_pickers[mouse_button].color, interpolate_spinboxes[mouse_button].value / 100)
brush_textures[mouse_button].create_from_image(brush_images[mouse_button], 0)
brush_type_buttons[mouse_button].get_child(0).texture = brush_textures[mouse_button]
func blend_image_with_color(image : Image, color : Color, interpolate_factor : float) -> Image:
var blended_image := Image.new()
blended_image.copy_from(image)
var size := image.get_size()
blended_image.lock()
for xx in size.x:
for yy in size.y:
if color.a > 0: # If it's the pencil
var current_color := blended_image.get_pixel(xx, yy)
if current_color.a > 0:
var new_color := current_color.linear_interpolate(color, interpolate_factor)
new_color.a = current_color.a
blended_image.set_pixel(xx, yy, new_color)
else: # If color is transparent - if it's the eraser
blended_image.set_pixel(xx, yy, Color(0, 0, 0, 0))
return blended_image
# Algorithm based on http://members.chello.at/easyfilter/bresenham.html
# This is not used for drawing, rather for finding the points required
# for the mouse cursor/position indicator
func plot_circle(r : int) -> Array:
var circle_points := []
var xm := 0
var ym := 0
var x := -r
var y := 0
var err := 2 - r * 2
while x < 0:
circle_points.append(Vector2(xm - x, ym + y))
circle_points.append(Vector2(xm - y, ym - x))
circle_points.append(Vector2(xm + x, ym - y))
circle_points.append(Vector2(xm + y, ym + x))
r = err
if r <= y:
y += 1
err += y * 2 + 1
if r > x || err > y:
x += 1
err += x * 2 + 1
return circle_points
func _exit_tree() -> void: func _exit_tree() -> void:
config_cache.set_value("window", "screen", OS.current_screen) config_cache.set_value("window", "screen", OS.current_screen)
config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen) config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen)

View file

@ -105,18 +105,7 @@ func add_randomised_brush(fpaths : Array, tooltip_name : String) -> void:
if len(loaded_images) > 0: # actually have images if len(loaded_images) > 0: # actually have images
# to use. # to use.
# take initial image... Brushes.add_file_brush(loaded_images, tooltip_name)
var first_image : Image = loaded_images.pop_front()
# The index which this random brush will be at
var next_random_brush_index : int = Global.file_brush_container.get_child_count()
Global.file_brushes.append(first_image)
Global.create_brush_button(first_image, Global.Brush_Types.RANDOM_FILE, tooltip_name)
# # Process the rest
for remaining_image in loaded_images:
var brush_button = Global.file_brush_container.get_child(next_random_brush_index)
brush_button.random_brushes.append(remaining_image)
# Add a plain brush from the given path to the list of brushes. # Add a plain brush from the given path to the list of brushes.
# Taken, again, from find_brushes # Taken, again, from find_brushes
@ -127,8 +116,7 @@ func add_plain_brush(path: String, tooltip_name: String) -> void:
return return
# do the standard conversion thing... # do the standard conversion thing...
image.convert(Image.FORMAT_RGBA8) image.convert(Image.FORMAT_RGBA8)
Global.file_brushes.append(image) Brushes.add_file_brush([image], tooltip_name)
Global.create_brush_button(image, Global.Brush_Types.FILE, tooltip_name)
# Import brushes, in priority order, from the paths in question in priority order # Import brushes, in priority order, from the paths in question in priority order
@ -214,8 +202,6 @@ func import_brushes(priority_ordered_search_path: Array) -> void:
# Mark this as a processed relpath # Mark this as a processed relpath
processed_subdir_paths[nonrandomised_subdir][relative_path] = true processed_subdir_paths[nonrandomised_subdir][relative_path] = true
Global.brushes_from_files = Global.file_brushes.size()
func import_patterns(priority_ordered_search_path: Array) -> void: func import_patterns(priority_ordered_search_path: Array) -> void:
for path in priority_ordered_search_path: for path in priority_ordered_search_path:
@ -235,25 +221,7 @@ func import_patterns(priority_ordered_search_path: Array) -> void:
var err := image.load(path.plus_file(pattern)) var err := image.load(path.plus_file(pattern))
if err == OK: if err == OK:
image.convert(Image.FORMAT_RGBA8) image.convert(Image.FORMAT_RGBA8)
Global.patterns.append(image) Global.patterns_popup.add(image)
Global.create_pattern_button(image, pattern)
if Global.patterns.size() > 0:
var image_size = Global.patterns[0].get_size()
Global.pattern_images[0] = Global.patterns[0]
var pattern_left_tex := ImageTexture.new()
pattern_left_tex.create_from_image(Global.pattern_images[0], 0)
Global.fill_pattern_containers[0].get_child(0).get_child(0).texture = pattern_left_tex
Global.fill_pattern_containers[0].get_child(2).get_child(1).max_value = image_size.x - 1
Global.fill_pattern_containers[0].get_child(3).get_child(1).max_value = image_size.y - 1
Global.pattern_images[1] = Global.patterns[0]
var pattern_right_tex := ImageTexture.new()
pattern_right_tex.create_from_image(Global.pattern_images[1], 0)
Global.fill_pattern_containers[1].get_child(0).get_child(0).texture = pattern_right_tex
Global.fill_pattern_containers[1].get_child(2).get_child(1).max_value = image_size.x - 1
Global.fill_pattern_containers[1].get_child(3).get_child(1).max_value = image_size.y - 1
func import_gpl(path : String, text : String) -> Palette: func import_gpl(path : String, text : String) -> Palette:

View file

@ -93,7 +93,7 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
var image := Image.new() var image := Image.new()
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer) image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
new_project.brushes.append(image) new_project.brushes.append(image)
Global.create_brush_button(image) Brushes.add_project_brush(image)
file.close() file.close()
if !empty_project: if !empty_project:
@ -232,19 +232,13 @@ func open_old_pxo_file(file : File, new_project : Project, first_line : String)
guide_line = file.get_line() guide_line = file.get_line()
# Load tool options # Load tool options
Global.color_pickers[0].color = file.get_var() file.get_var()
Global.color_pickers[1].color = file.get_var() file.get_var()
Global.brush_sizes[0] = file.get_8() file.get_8()
Global.brush_size_edits[0].value = Global.brush_sizes[0] file.get_8()
Global.brush_sizes[1] = file.get_8()
Global.brush_size_edits[1].value = Global.brush_sizes[1]
if file_major_version == 0 and file_minor_version < 7: if file_major_version == 0 and file_minor_version < 7:
var left_palette = file.get_var() file.get_var()
var right_palette = file.get_var() file.get_var()
for color in left_palette:
Global.color_pickers[0].get_picker().add_preset(color)
for color in right_palette:
Global.color_pickers[1].get_picker().add_preset(color)
# Load custom brushes # Load custom brushes
var brush_line := file.get_line() var brush_line := file.get_line()
@ -255,7 +249,7 @@ func open_old_pxo_file(file : File, new_project : Project, first_line : String)
var image := Image.new() var image := Image.new()
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer) image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
new_project.brushes.append(image) new_project.brushes.append(image)
Global.create_brush_button(image) Brushes.add_project_brush(image)
brush_line = file.get_line() brush_line = file.get_line()
if file_major_version >= 0 and file_minor_version > 6: if file_major_version >= 0 and file_minor_version > 6:

201
src/Autoload/Tools.gd Normal file
View 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

View file

@ -5,17 +5,9 @@ extends Node2D
var location := Vector2.ZERO var location := Vector2.ZERO
var fill_color := Color(0, 0, 0, 0) var fill_color := Color(0, 0, 0, 0)
var current_pixel := Vector2.ZERO # pretty much same as mouse_pos, but can be accessed externally var current_pixel := Vector2.ZERO # pretty much same as mouse_pos, but can be accessed externally
var previous_mouse_pos := Vector2.ZERO
var previous_mouse_pos_for_lines := Vector2.ZERO
var can_undo := true var can_undo := true
var cursor_image_has_changed := false var cursor_image_has_changed := false
var previous_action := -1
var sprite_changed_this_frame := false # for optimization purposes var sprite_changed_this_frame := false # for optimization purposes
var is_making_line := false
var made_line := false
var is_making_selection := -1
var line_pos = []
var pen_pressure := 1.0 # For tablet pressure sensitivity
# Called when the node enters the scene tree for the first time. # Called when the node enters the scene tree for the first time.
@ -23,7 +15,6 @@ func _ready() -> void:
var frame : Frame = new_empty_frame(true) var frame : Frame = new_empty_frame(true)
Global.current_project.frames.append(frame) Global.current_project.frames.append(frame)
camera_zoom() camera_zoom()
line_pos = [previous_mouse_pos_for_lines, previous_mouse_pos_for_lines]
func _draw() -> void: func _draw() -> void:
@ -54,111 +45,40 @@ func _draw() -> void:
draw_grid(Global.grid_type) draw_grid(Global.grid_type)
# Draw rectangle to indicate the pixel currently being hovered on # Draw rectangle to indicate the pixel currently being hovered on
if Global.can_draw: if Global.has_focus and Global.can_draw:
var mouse_pos := current_pixel Tools.draw_indicator()
mouse_pos = mouse_pos.floor()
var visible_indicators := [Global.left_square_indicator_visible, Global.right_square_indicator_visible]
for i in range(0, 1):
if visible_indicators[i]:
if Global.current_brush_types[i] == Global.Brush_Types.PIXEL || Global.current_tools[i] == Global.Tools.LIGHTENDARKEN:
if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER || Global.current_tools[i] == Global.Tools.LIGHTENDARKEN:
var start_pos_x = mouse_pos.x - (Global.brush_sizes[i] >> 1)
var start_pos_y = mouse_pos.y - (Global.brush_sizes[i] >> 1)
draw_rect(Rect2(start_pos_x, start_pos_y, Global.brush_sizes[i], Global.brush_sizes[i]), Color.blue, false)
#Check for tile mode
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
var new_start_pos_x = posmod(start_pos_x,Global.current_project.size.x)
var new_start_pos_y = posmod(start_pos_y,Global.current_project.size.y)
draw_rect(Rect2(new_start_pos_x, new_start_pos_y, Global.brush_sizes[i], Global.brush_sizes[i]), Color.green, false)
if is_making_line:
var line_rect = plot_line(line_pos[1], line_pos[0])
for rect in line_rect:
draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false)
#Check for tile mode
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
rect.x = posmod(rect.x,Global.current_project.size.x)
rect.y = posmod(rect.y,Global.current_project.size.y)
draw_rect(Rect2(rect, Vector2.ONE), Color.green, false)
elif Global.current_brush_types[i] == Global.Brush_Types.CIRCLE || Global.current_brush_types[i] == Global.Brush_Types.FILLED_CIRCLE:
if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER:
draw_set_transform(mouse_pos, rotation, scale)
for rect in Global.left_circle_points:
draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false)
#Check for tile mode
if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)):
if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)):
var pos = mouse_pos.posmodv(Global.current_project.size)
if pos != mouse_pos:
draw_set_transform(pos,rotation,scale)
for rect in Global.left_circle_points:
draw_rect(Rect2(rect, Vector2.ONE), Color.green, false)
draw_set_transform(position, rotation, scale)
else:
if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER:
var custom_brush_size = Global.brush_images[i].get_size() - Vector2.ONE
var dst : Vector2 = DrawingAlgos.rectangle_center(mouse_pos, custom_brush_size)
draw_texture(Global.brush_textures[i], dst)
func _input(event : InputEvent) -> void: func _input(event : InputEvent) -> void:
# Don't process anything below if the input isn't a mouse event, or Shift/Ctrl. # Don't process anything below if the input isn't a mouse event, or Shift/Ctrl.
# This decreases CPU/GPU usage slightly. # This decreases CPU/GPU usage slightly.
if not event is InputEventMouse: if not event is InputEventMouse:
if event is InputEventKey: if not event is InputEventKey:
if event.scancode != KEY_SHIFT && event.scancode != KEY_CONTROL:
return
else:
return return
elif not event.scancode in [KEY_SHIFT, KEY_CONTROL]:
if (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")): return
made_line = false # elif not get_viewport_rect().has_point(event.position):
DrawingAlgos.reset() # return
can_undo = true
current_pixel = get_local_mouse_position() + location current_pixel = get_local_mouse_position() + location
if Global.has_focus: if Global.has_focus:
update() update()
# Godot 3.2 and above only code
if Engine.get_version_info().major == 3 && Engine.get_version_info().minor >= 2:
if event is InputEventMouseMotion:
if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.NONE:
pen_pressure = 1
else:
pen_pressure = event.pressure
sprite_changed_this_frame = false sprite_changed_this_frame = false
var mouse_pos := current_pixel
var mouse_pos_floored := mouse_pos.floor()
var current_mouse_button := -1
var current_project : Project = Global.current_project
var current_project : Project = Global.current_project
current_project.x_min = location.x current_project.x_min = location.x
current_project.x_max = location.x + current_project.size.x current_project.x_max = location.x + current_project.size.x
current_project.y_min = location.y current_project.y_min = location.y
current_project.y_max = location.y + current_project.size.y current_project.y_max = location.y + current_project.size.y
if current_project.selected_pixels.size() != 0: if not current_project.selected_rect.has_no_area():
current_project.x_min = max(current_project.x_min, Global.selection_rectangle.polygon[0].x) current_project.x_min = max(current_project.x_min, current_project.selected_rect.position.x)
current_project.x_max = min(current_project.x_max, Global.selection_rectangle.polygon[2].x) current_project.x_max = min(current_project.x_max, current_project.selected_rect.end.x)
current_project.y_min = max(current_project.y_min, Global.selection_rectangle.polygon[0].y) current_project.y_min = max(current_project.y_min, current_project.selected_rect.position.y)
current_project.y_max = min(current_project.y_max, Global.selection_rectangle.polygon[2].y) current_project.y_max = min(current_project.y_max, current_project.selected_rect.end.y)
if Input.is_mouse_button_pressed(BUTTON_LEFT):
current_mouse_button = Global.Mouse_Button.LEFT
elif Input.is_mouse_button_pressed(BUTTON_RIGHT):
current_mouse_button = Global.Mouse_Button.RIGHT
var current_action : int = Global.current_tools[current_mouse_button] if current_mouse_button != -1 else -1
if Global.has_focus: if Global.has_focus:
Global.cursor_position_label.text = "[%s×%s] %s, %s" % [current_project.size.x, current_project.size.y, mouse_pos_floored.x, mouse_pos_floored.y]
if !cursor_image_has_changed: if !cursor_image_has_changed:
cursor_image_has_changed = true cursor_image_has_changed = true
if Global.show_left_tool_icon: if Global.show_left_tool_icon:
@ -166,80 +86,13 @@ func _input(event : InputEvent) -> void:
if Global.show_right_tool_icon: if Global.show_right_tool_icon:
Global.right_cursor.visible = true Global.right_cursor.visible = true
else: else:
Global.cursor_position_label.text = "[%s×%s]" % [current_project.size.x, current_project.size.y]
if cursor_image_has_changed: if cursor_image_has_changed:
cursor_image_has_changed = false cursor_image_has_changed = false
Global.left_cursor.visible = false Global.left_cursor.visible = false
Global.right_cursor.visible = false Global.right_cursor.visible = false
# Handle Undo/Redo Tools.handle_draw(current_pixel.floor(), event)
var can_handle : bool = Global.can_draw && Global.has_focus && !made_line
var mouse_pressed : bool = (Input.is_action_just_pressed("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_pressed("right_mouse") && !Input.is_action_pressed("left_mouse"))
if mouse_pressed:
if can_handle || is_making_line:
if current_action != -1 && current_action != Global.Tools.COLORPICKER && current_action != Global.Tools.ZOOM:
if current_action == Global.Tools.RECTSELECT:
handle_undo("Rectangle Select")
else:
handle_undo("Draw")
elif (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")):
if can_handle || current_project.undos == current_project.undo_redo.get_version():
if previous_action != -1 && previous_action != Global.Tools.RECTSELECT && current_action != Global.Tools.COLORPICKER && current_action != Global.Tools.ZOOM:
handle_redo("Draw")
handle_tools(current_mouse_button, current_action, mouse_pos, can_handle)
if Global.can_draw && Global.has_focus && Input.is_action_just_pressed("shift") && ([Global.Tools.PENCIL, Global.Tools.ERASER, Global.Tools.LIGHTENDARKEN].has(Global.current_tools[0]) || [Global.Tools.PENCIL, Global.Tools.ERASER, Global.Tools.LIGHTENDARKEN].has(Global.current_tools[1])):
is_making_line = true
line_pos[0] = previous_mouse_pos_for_lines
elif Input.is_action_just_released("shift"):
is_making_line = false
line_pos[1] = line_pos[0]
if is_making_line:
var point0 : Vector2 = line_pos[0]
var angle := stepify(rad2deg(mouse_pos.angle_to_point(point0)), 0.01)
if Input.is_action_pressed("ctrl"):
angle = round(angle / 15) * 15
var distance : float = point0.distance_to(mouse_pos)
line_pos[1] = point0 + Vector2.RIGHT.rotated(deg2rad(angle)) * distance
else:
line_pos[1] = mouse_pos
if angle < 0:
angle = 360 + angle
Global.cursor_position_label.text += " %s°" % str(angle)
if is_making_selection != -1: # If we're making a selection
var mouse_button_string := "left_mouse" if is_making_selection == Global.Mouse_Button.LEFT else "right_mouse"
if Input.is_action_just_released(mouse_button_string): # Finish selection when button is released
var start_pos = Global.selection_rectangle.polygon[0]
var end_pos = Global.selection_rectangle.polygon[2]
if start_pos.x > end_pos.x:
var temp = end_pos.x
end_pos.x = start_pos.x
start_pos.x = temp
if start_pos.y > end_pos.y:
var temp = end_pos.y
end_pos.y = start_pos.y
start_pos.y = temp
Global.selection_rectangle.polygon[0] = start_pos
Global.selection_rectangle.polygon[1] = Vector2(end_pos.x, start_pos.y)
Global.selection_rectangle.polygon[2] = end_pos
Global.selection_rectangle.polygon[3] = Vector2(start_pos.x, end_pos.y)
for xx in range(start_pos.x, end_pos.x):
for yy in range(start_pos.y, end_pos.y):
current_project.selected_pixels.append(Vector2(xx, yy))
is_making_selection = -1
handle_redo("Rectangle Select")
previous_action = current_action
previous_mouse_pos = current_pixel
if sprite_changed_this_frame: if sprite_changed_this_frame:
update_texture(current_project.current_layer) update_texture(current_project.current_layer)
@ -284,143 +137,6 @@ func new_empty_frame(first_time := false, single_layer := false, size := Global.
return frame return frame
func handle_tools(current_mouse_button : int, current_action : int, mouse_pos : Vector2, can_handle : bool) -> void:
var current_project : Project = Global.current_project
var current_cel : Cel = current_project.frames[current_project.current_frame].cels[current_project.current_layer]
var sprite : Image = current_cel.image
var mouse_pos_floored := mouse_pos.floor()
var mouse_pos_ceiled := mouse_pos.ceil()
var current_color : Color = Global.color_pickers[current_mouse_button].color
var fill_area : int = Global.fill_areas[current_mouse_button]
var ld : int = Global.ld_modes[current_mouse_button]
var ld_amount : float = Global.ld_amounts[current_mouse_button]
var color_picker_for : int = Global.color_picker_for[current_mouse_button]
var zoom_mode : int = Global.zoom_modes[current_mouse_button]
match current_action: # Handle current tool
Global.Tools.PENCIL:
pencil_and_eraser(sprite, mouse_pos, current_color, current_mouse_button, current_action)
Global.Tools.ERASER:
pencil_and_eraser(sprite, mouse_pos, Color(0, 0, 0, 0), current_mouse_button, current_action)
Global.Tools.BUCKET:
if can_handle:
var fill_with : int = Global.fill_with[current_mouse_button]
var pattern_image : Image = Global.pattern_images[current_mouse_button]
var pattern_offset : Vector2 = Global.fill_pattern_offsets[current_mouse_button]
if fill_area == Global.Fill_Area.SAME_COLOR_AREA: # Paint the specific area of the same color
var mirror_x := current_project.x_max + current_project.x_min - mouse_pos_floored.x - 1
var mirror_y := current_project.y_max + current_project.y_min - mouse_pos_floored.y - 1
var horizontal_mirror : bool = Global.horizontal_mirror[current_mouse_button]
var vertical_mirror : bool = Global.vertical_mirror[current_mouse_button]
if fill_with == Global.Fill_With.PATTERN && pattern_image: # Pattern fill
DrawingAlgos.pattern_fill(sprite, mouse_pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
if horizontal_mirror:
var pos := Vector2(mirror_x, mouse_pos.y)
DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
if vertical_mirror:
var pos := Vector2(mouse_pos.x, mirror_y)
DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
if horizontal_mirror && vertical_mirror:
var pos := Vector2(mirror_x, mirror_y)
DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset)
else: # Flood fill
DrawingAlgos.flood_fill(sprite, mouse_pos, sprite.get_pixelv(mouse_pos), current_color)
if horizontal_mirror:
var pos := Vector2(mirror_x, mouse_pos.y)
DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color)
if vertical_mirror:
var pos := Vector2(mouse_pos.x, mirror_y)
DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color)
if horizontal_mirror && vertical_mirror:
var pos := Vector2(mirror_x, mirror_y)
DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color)
else: # Paint all pixels of the same color
var pixel_color : Color = sprite.get_pixelv(mouse_pos)
for xx in range(current_project.x_min, current_project.x_max):
for yy in range(current_project.y_min, current_project.y_max):
var c : Color = sprite.get_pixel(xx, yy)
if c == pixel_color:
if fill_with == Global.Fill_With.PATTERN && pattern_image: # Pattern fill
pattern_image.lock()
var pattern_size := pattern_image.get_size()
var xxx : int = int(xx + pattern_offset.x) % int(pattern_size.x)
var yyy : int = int(yy + pattern_offset.y) % int(pattern_size.y)
var pattern_color : Color = pattern_image.get_pixel(xxx, yyy)
sprite.set_pixel(xx, yy, pattern_color)
pattern_image.unlock()
else:
sprite.set_pixel(xx, yy, current_color)
sprite_changed_this_frame = true
Global.Tools.LIGHTENDARKEN:
if can_handle:
var pixel_color : Color = sprite.get_pixelv(mouse_pos)
var color_changed : Color
if ld == Global.Lighten_Darken_Mode.LIGHTEN:
color_changed = pixel_color.lightened(ld_amount)
else: # Darken
color_changed = pixel_color.darkened(ld_amount)
pencil_and_eraser(sprite, mouse_pos, color_changed, current_mouse_button, current_action)
Global.Tools.RECTSELECT:
# Check SelectionRectangle.gd for more code on Rectangle Selection
if Global.can_draw && Global.has_focus:
# If we're creating a new selection
if current_project.selected_pixels.size() == 0 || !point_in_rectangle(mouse_pos_floored, Global.selection_rectangle.polygon[0] - Vector2.ONE, Global.selection_rectangle.polygon[2]):
var mouse_button_string := "left_mouse" if current_mouse_button == Global.Mouse_Button.LEFT else "right_mouse"
if Input.is_action_just_pressed(mouse_button_string):
Global.selection_rectangle.polygon[0] = mouse_pos_floored
Global.selection_rectangle.polygon[1] = mouse_pos_floored
Global.selection_rectangle.polygon[2] = mouse_pos_floored
Global.selection_rectangle.polygon[3] = mouse_pos_floored
is_making_selection = current_mouse_button
current_project.selected_pixels.clear()
else:
if is_making_selection != -1: # If we're making a new selection...
var start_pos = Global.selection_rectangle.polygon[0]
if start_pos != mouse_pos_floored:
var end_pos := Vector2(mouse_pos_ceiled.x, mouse_pos_ceiled.y)
if mouse_pos.x < start_pos.x:
end_pos.x = mouse_pos_ceiled.x - 1
if mouse_pos.y < start_pos.y:
end_pos.y = mouse_pos_ceiled.y - 1
Global.selection_rectangle.polygon[1] = Vector2(end_pos.x, start_pos.y)
Global.selection_rectangle.polygon[2] = end_pos
Global.selection_rectangle.polygon[3] = Vector2(start_pos.x, end_pos.y)
Global.Tools.COLORPICKER:
var canvas_rect := Rect2(location, current_project.size)
if can_handle && canvas_rect.has_point(mouse_pos):
var image_data := Image.new()
image_data.copy_from(sprite)
image_data.lock()
var pixel_color : Color = image_data.get_pixelv(mouse_pos)
Global.color_pickers[color_picker_for].color = pixel_color
Global.update_custom_brush(color_picker_for)
Global.Tools.ZOOM:
if can_handle:
if zoom_mode == Global.Zoom_Mode.ZOOM_IN:
Global.camera.zoom_camera(-1)
else:
Global.camera.zoom_camera(1)
func pencil_and_eraser(sprite : Image, mouse_pos : Vector2, color : Color, current_mouse_button : int, current_action := -1) -> void:
if made_line:
return
if is_making_line:
DrawingAlgos.fill_gaps(sprite, line_pos[1], previous_mouse_pos_for_lines, color, current_mouse_button, pen_pressure, current_action)
DrawingAlgos.draw_brush(sprite, line_pos[1], color, current_mouse_button, pen_pressure, current_action)
made_line = true
else:
# Draw
DrawingAlgos.draw_brush(sprite, mouse_pos, color, current_mouse_button, pen_pressure, current_action)
DrawingAlgos.fill_gaps(sprite, mouse_pos, previous_mouse_pos, color, current_mouse_button, pen_pressure, current_action) # Fill the gaps
func handle_undo(action : String) -> void: func handle_undo(action : String) -> void:
if !can_undo: if !can_undo:
return return
@ -442,16 +158,12 @@ func handle_undo(action : String) -> void:
var data = f.cels[Global.current_project.current_layer].image.data var data = f.cels[Global.current_project.current_layer].image.data
f.cels[Global.current_project.current_layer].image.lock() f.cels[Global.current_project.current_layer].image.lock()
Global.current_project.undo_redo.add_undo_property(f.cels[Global.current_project.current_layer].image, "data", data) Global.current_project.undo_redo.add_undo_property(f.cels[Global.current_project.current_layer].image, "data", data)
if action == "Rectangle Select":
var selected_pixels = Global.current_project.selected_pixels.duplicate()
Global.current_project.undo_redo.add_undo_property(Global.selection_rectangle, "polygon", Global.selection_rectangle.polygon)
Global.current_project.undo_redo.add_undo_property(Global.current_project, "selected_pixels", selected_pixels)
Global.current_project.undo_redo.add_undo_method(Global, "undo", frame_index, layer_index) Global.current_project.undo_redo.add_undo_method(Global, "undo", frame_index, layer_index)
can_undo = false can_undo = false
func handle_redo(action : String) -> void: func handle_redo(_action : String) -> void:
can_undo = true can_undo = true
if Global.current_project.undos < Global.current_project.undo_redo.get_version(): if Global.current_project.undos < Global.current_project.undo_redo.get_version():
@ -467,9 +179,6 @@ func handle_redo(action : String) -> void:
frames = Global.current_project.frames frames = Global.current_project.frames
for f in frames: for f in frames:
Global.current_project.undo_redo.add_do_property(f.cels[Global.current_project.current_layer].image, "data", f.cels[Global.current_project.current_layer].image.data) Global.current_project.undo_redo.add_do_property(f.cels[Global.current_project.current_layer].image, "data", f.cels[Global.current_project.current_layer].image.data)
if action == "Rectangle Select":
Global.current_project.undo_redo.add_do_property(Global.selection_rectangle, "polygon", Global.selection_rectangle.polygon)
Global.current_project.undo_redo.add_do_property(Global.current_project, "selected_pixels", Global.current_project.selected_pixels)
Global.current_project.undo_redo.add_do_method(Global, "redo", frame_index, layer_index) Global.current_project.undo_redo.add_do_method(Global, "redo", frame_index, layer_index)
Global.current_project.undo_redo.commit_action() Global.current_project.undo_redo.commit_action()
@ -561,34 +270,3 @@ func draw_grid(grid_type : int) -> void:
for x in range(0, size.x, Global.grid_height * 2): for x in range(0, size.x, Global.grid_height * 2):
var yy2 = (size.x - x) * tan(deg2rad(26.565)) # 30 degrees var yy2 = (size.x - x) * tan(deg2rad(26.565)) # 30 degrees
draw_line(Vector2(x, size.y), Vector2(size.x, size.y - yy2), Global.grid_color) draw_line(Vector2(x, size.y), Vector2(size.x, size.y - yy2), Global.grid_color)
# Bresenham's Algorithm
# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency
func plot_line(end_pos : Vector2, start_pos : Vector2) -> Array:
start_pos = start_pos.floor()
end_pos = end_pos.floor()
var line_points := []
var dx := int(abs(end_pos.x - start_pos.x))
var dy := int(-abs(end_pos.y - start_pos.y))
var err := dx + dy
var e2 := err << 1 # err * 2
var sx = 1 if start_pos.x < end_pos.x else -1
var sy = 1 if start_pos.y < end_pos.y else -1
var x = start_pos.x
var y = start_pos.y
while !(x == end_pos.x && y == end_pos.y):
line_points.append(Vector2(x, y))
e2 = err << 1
if e2 >= dy:
err += dy
x += sx
if e2 <= dx:
err += dx
y += sy
return line_points
# Checks if a point is inside a rectangle
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y

View file

@ -1,8 +1,20 @@
class_name Drawer class_name Drawer
class ColorOp:
var strength := 1.0
func process(src: Color, _dst: Color) -> Color:
return src
class SimpleDrawer: class SimpleDrawer:
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void: func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void:
_sprite.set_pixel(_pos.x, _pos.y, _new_color) var color_old := image.get_pixelv(position)
var color_new := op.process(color, color_old)
if not color_new.is_equal_approx(color_old):
image.set_pixelv(position, color_new)
class PixelPerfectDrawer: class PixelPerfectDrawer:
@ -15,9 +27,10 @@ class PixelPerfectDrawer:
last_pixels = [null, null] last_pixels = [null, null]
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void: func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void:
last_pixels.push_back([_pos, _sprite.get_pixel(_pos.x, _pos.y)]) var color_old = image.get_pixelv(position)
_sprite.set_pixel(_pos.x, _pos.y, _new_color) last_pixels.push_back([position, color_old])
image.set_pixelv(position, op.process(color, color_old))
var corner = last_pixels.pop_front() var corner = last_pixels.pop_front()
var neighbour = last_pixels[0] var neighbour = last_pixels[0]
@ -25,14 +38,15 @@ class PixelPerfectDrawer:
if corner == null or neighbour == null: if corner == null or neighbour == null:
return return
if _pos - corner[0] in corners and _pos - neighbour[0] in neighbours: if position - corner[0] in corners and position - neighbour[0] in neighbours:
_sprite.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1]) image.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
last_pixels[0] = corner last_pixels[0] = corner
var pixel_perfect := false setget set_pixel_perfect var pixel_perfect := false setget set_pixel_perfect
var h_mirror := false var horizontal_mirror := false
var v_mirror := false var vertical_mirror := false
var color_op := ColorOp.new()
var simple_drawer := SimpleDrawer.new() var simple_drawer := SimpleDrawer.new()
var pixel_perfect_drawers = [PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new()] var pixel_perfect_drawers = [PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new()]
@ -52,14 +66,14 @@ func set_pixel_perfect(value: bool) -> void:
drawers = [simple_drawer, simple_drawer, simple_drawer, simple_drawer] drawers = [simple_drawer, simple_drawer, simple_drawer, simple_drawer]
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void: func set_pixel(image: Image, position: Vector2, color: Color) -> void:
var mirror_x = Global.current_project.x_max + Global.current_project.x_min - _pos.x - 1 var mirror_x = Global.current_project.x_max + Global.current_project.x_min - position.x - 1
var mirror_y = Global.current_project.y_max + Global.current_project.y_min - _pos.y - 1 var mirror_y = Global.current_project.y_max + Global.current_project.y_min - position.y - 1
drawers[0].set_pixel(_sprite, _pos, _new_color) drawers[0].set_pixel(image, position, color, color_op)
if h_mirror: if horizontal_mirror:
drawers[1].set_pixel(_sprite, Vector2(mirror_x, _pos.y), _new_color) drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op)
if v_mirror: if vertical_mirror:
drawers[2].set_pixel(_sprite, Vector2(mirror_x, mirror_y), _new_color) drawers[2].set_pixel(image, Vector2(mirror_x, mirror_y), color, color_op)
if v_mirror: if vertical_mirror:
drawers[3].set_pixel(_sprite, Vector2(_pos.x, mirror_y), _new_color) drawers[3].set_pixel(image, Vector2(position.x, mirror_y), color, color_op)

View file

@ -16,12 +16,13 @@ var guides := [] # Array of Guides
var brushes := [] # Array of Images var brushes := [] # Array of Images
var selected_pixels := []
var x_min := 0 var x_min := 0
var x_max := 64 var x_max := 64
var y_min := 0 var y_min := 0
var y_max := 64 var y_max := 64
var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect
# For every camera (currently there are 3) # For every camera (currently there are 3)
var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2 var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2
var cameras_offset := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO] # Array of Vector2 var cameras_offset := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO] # Array of Vector2
@ -39,6 +40,11 @@ func _init(_frames := [], _name := tr("untitled")) -> void:
OpenSave.backup_save_paths.append("") OpenSave.backup_save_paths.append("")
func _set_selected_rect(value : Rect2) -> void:
selected_rect = value
Global.selection_rectangle.set_rect(value)
func change_project() -> void: func change_project() -> void:
# Remove old nodes # Remove old nodes
for container in Global.layers_container.get_children(): for container in Global.layers_container.get_children():
@ -94,16 +100,7 @@ func change_project() -> void:
self.animation_tags = animation_tags self.animation_tags = animation_tags
# Change the selection rectangle # Change the selection rectangle
if selected_pixels.size() != 0: Global.selection_rectangle.set_rect(selected_rect)
Global.selection_rectangle.polygon[0] = Vector2(x_min, y_min)
Global.selection_rectangle.polygon[1] = Vector2(x_max, y_min)
Global.selection_rectangle.polygon[2] = Vector2(x_max, y_max)
Global.selection_rectangle.polygon[3] = Vector2(x_min, y_max)
else:
Global.selection_rectangle.polygon[0] = Vector2.ZERO
Global.selection_rectangle.polygon[1] = Vector2.ZERO
Global.selection_rectangle.polygon[2] = Vector2.ZERO
Global.selection_rectangle.polygon[3] = Vector2.ZERO
# Change the guides # Change the guides
for guide in Global.canvas.get_children(): for guide in Global.canvas.get_children():
@ -114,11 +111,9 @@ func change_project() -> void:
guide.visible = false guide.visible = false
# Change the project brushes # Change the project brushes
for child in Global.project_brush_container.get_children(): Brushes.clear_project_brush()
child.queue_free()
for brush in brushes: for brush in brushes:
Global.create_brush_button(brush) Brushes.add_project_brush(brush)
var cameras = [Global.camera, Global.camera2, Global.camera_preview] var cameras = [Global.camera, Global.camera2, Global.camera_preview]
var i := 0 var i := 0

View file

@ -19,9 +19,6 @@ func _ready() -> void:
Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order()) Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order())
Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order()) Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order())
Global.color_pickers[0].get_picker().presets_visible = false
Global.color_pickers[1].get_picker().presets_visible = false
$QuitAndSaveDialog.add_button("Save & Exit", false, "Save") $QuitAndSaveDialog.add_button("Save & Exit", false, "Save")
$QuitAndSaveDialog.get_ok().text = "Exit without saving" $QuitAndSaveDialog.get_ok().text = "Exit without saving"

View file

@ -27,8 +27,8 @@ func open(palette : String) -> void:
self.popup_centered() self.popup_centered()
Global.dialog_open(true) Global.dialog_open(true)
left_color_button.modulate = Global.color_pickers[0].color left_color_button.modulate = Tools.get_assigned_color(BUTTON_LEFT)
right_color_button.modulate = Global.color_pickers[1].color right_color_button.modulate = Tools.get_assigned_color(BUTTON_RIGHT)
func _display_palette() -> void: func _display_palette() -> void:
@ -177,12 +177,12 @@ func _refresh_hint_tooltip(_index : int) -> void:
func _on_LeftColor_pressed() -> void: func _on_LeftColor_pressed() -> void:
color_picker.color = Global.color_pickers[0].color color_picker.color = Tools.get_assigned_color(BUTTON_LEFT)
_on_EditPaletteColorPicker_color_changed(color_picker.color) _on_EditPaletteColorPicker_color_changed(color_picker.color)
func _on_RightColor_pressed() -> void: func _on_RightColor_pressed() -> void:
color_picker.color = Global.color_pickers[1].color color_picker.color = Tools.get_assigned_color(BUTTON_RIGHT)
_on_EditPaletteColorPicker_color_changed(color_picker.color) _on_EditPaletteColorPicker_color_changed(color_picker.color)

View file

@ -220,11 +220,9 @@ func on_color_select(index : int) -> void:
var color : Color = Global.palettes[current_palette].get_color(index) var color : Color = Global.palettes[current_palette].get_color(index)
if Input.is_action_just_pressed("left_mouse"): if Input.is_action_just_pressed("left_mouse"):
Global.color_pickers[0].color = color Tools.assign_color(color, BUTTON_LEFT)
Global.update_custom_brush(0)
elif Input.is_action_just_pressed("right_mouse"): elif Input.is_action_just_pressed("right_mouse"):
Global.color_pickers[1].color = color Tools.assign_color(color, BUTTON_RIGHT)
Global.update_custom_brush(1)
func _load_palettes() -> void: func _load_palettes() -> void:

View file

@ -1,144 +1,156 @@
extends Polygon2D extends Polygon2D
var img : Image
var tex : ImageTexture var _selected_rect := Rect2(0, 0, 0, 0)
var is_dragging := false var _clipped_rect := Rect2(0, 0, 0, 0)
var move_pixels := false var _move_image := Image.new()
var diff_x := 0.0 var _move_texture := ImageTexture.new()
var diff_y := 0.0 var _clear_image := Image.new()
var orig_x := 0.0 var _move_pixel := false
var orig_y := 0.0 var _clipboard := Image.new()
var orig_colors := [] var _undo_data := {}
func _ready() -> void: func _ready() -> void:
img = Image.new() _clear_image.create(1, 1, false, Image.FORMAT_RGBA8)
img.create(1, 1, false, Image.FORMAT_RGBA8) _clear_image.fill(Color(0, 0, 0, 0))
img.lock()
tex = ImageTexture.new()
tex.create_from_image(img, 0)
func _process(_delta : float) -> void:
if Global.current_project.layers[Global.current_project.current_layer].locked:
return
var mouse_pos: Vector2 = get_local_mouse_position() - Global.canvas.location
var mouse_pos_floored := mouse_pos.floor()
var start_pos := polygon[0]
var end_pos := polygon[2]
var current_layer_index : int = Global.current_project.current_layer
var layer : Image = Global.current_project.frames[Global.current_project.current_frame].cels[current_layer_index].image
if end_pos == start_pos:
visible = false
else:
visible = true
if Global.can_draw and Global.has_focus and point_in_rectangle(mouse_pos, polygon[0], polygon[2]) and Global.current_project.selected_pixels.size() > 0 and (Global.current_tools[0] == Global.Tools.RECTSELECT or Global.current_tools[1] == Global.Tools.RECTSELECT):
get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_MOVE
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_just_pressed("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_just_pressed("right_mouse")):
# Begin dragging
is_dragging = true
if Input.is_key_pressed(KEY_SHIFT):
move_pixels = true
else:
move_pixels = false
img.fill(Color(0, 0, 0, 0))
diff_x = end_pos.x - mouse_pos_floored.x
diff_y = end_pos.y - mouse_pos_floored.y
orig_x = start_pos.x - mouse_pos_floored.x
orig_y = start_pos.y - mouse_pos_floored.y
if move_pixels:
img.unlock()
img.resize(polygon[2].x - polygon[0].x, polygon[2].y - polygon[0].y, 0)
img.lock()
for i in range(Global.current_project.selected_pixels.size()):
var curr_px = Global.current_project.selected_pixels[i]
if point_in_rectangle(curr_px, Global.canvas.location - Vector2.ONE, Global.current_project.size):
orig_colors.append(layer.get_pixelv(curr_px)) # Color of pixel
var px = curr_px - Global.current_project.selected_pixels[0]
img.set_pixelv(px, orig_colors[i])
layer.set_pixelv(curr_px, Color(0, 0, 0, 0))
else: # If part of selection is outside canvas
orig_colors.append(Color(0, 0, 0, 0))
Global.canvas.update_texture(current_layer_index)
tex.create_from_image(img, 0)
update()
else:
get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_CROSS
if is_dragging:
if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_pressed("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_pressed("right_mouse")):
# Drag
start_pos.x = orig_x + mouse_pos_floored.x
end_pos.x = diff_x + mouse_pos_floored.x
start_pos.y = orig_y + mouse_pos_floored.y
end_pos.y = diff_y + mouse_pos_floored.y
polygon[0] = start_pos
polygon[1] = Vector2(end_pos.x, start_pos.y)
polygon[2] = end_pos
polygon[3] = Vector2(start_pos.x, end_pos.y)
if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_just_released("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_just_released("right_mouse")):
# Release Drag
is_dragging = false
if move_pixels:
for i in range(orig_colors.size()):
if orig_colors[i].a > 0:
var px = polygon[0] + Global.current_project.selected_pixels[i] - Global.current_project.selected_pixels[0]
if point_in_rectangle(px, Global.canvas.location - Vector2.ONE, Global.current_project.size):
layer.set_pixelv(px, orig_colors[i])
Global.canvas.update_texture(current_layer_index)
img.fill(Color(0, 0, 0, 0))
tex.create_from_image(img, 0)
update()
orig_colors.clear()
Global.current_project.selected_pixels.clear()
for xx in range(start_pos.x, end_pos.x):
for yy in range(start_pos.y, end_pos.y):
Global.current_project.selected_pixels.append(Vector2(xx, yy))
Global.canvas.handle_redo("Rectangle Select") # Redo
if Global.current_project.selected_pixels.size() > 0:
# Handle copy
if Input.is_action_just_pressed("copy"):
# Save as custom brush
var brush_img := Image.new()
brush_img = layer.get_rect(Rect2(polygon[0], polygon[2] - polygon[0]))
if brush_img.is_invisible():
return
brush_img = brush_img.get_rect(brush_img.get_used_rect()) # Save only the visible pixels
Global.current_project.brushes.append(brush_img)
Global.create_brush_button(brush_img)
# Have it in the clipboard so it can be pasted later
Global.image_clipboard = layer.get_rect(Rect2(polygon[0], polygon[2] - polygon[0]))
# Handle paste
if Input.is_action_just_pressed("paste") && Global.image_clipboard.get_size() > Vector2.ZERO:
Global.canvas.handle_undo("Draw")
layer.blend_rect(Global.image_clipboard, Rect2(Vector2.ZERO, polygon[2]-polygon[0]), polygon[0])
layer.lock()
Global.canvas.handle_redo("Draw")
if Input.is_action_just_pressed("delete"):
Global.canvas.handle_undo("Draw")
for xx in range(start_pos.x, end_pos.x):
for yy in range(start_pos.y, end_pos.y):
if point_in_rectangle(Vector2(xx, yy), Global.canvas.location - Vector2.ONE, Global.canvas.location + Global.current_project.size):
layer.set_pixel(xx, yy, Color(0, 0, 0, 0))
Global.canvas.handle_redo("Draw")
func _draw() -> void: func _draw() -> void:
if img.get_size() == polygon[2] - polygon[0]: if _move_pixel:
draw_texture(tex, polygon[0], Color(1, 1, 1, 0.5)) draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5))
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool: func has_point(position : Vector2) -> bool:
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y return _selected_rect.has_point(position)
func get_rect() -> Rect2:
return _selected_rect
func set_rect(rect : Rect2) -> void:
_selected_rect = rect
polygon[0] = rect.position
polygon[1] = Vector2(rect.end.x, rect.position.y)
polygon[2] = rect.end
polygon[3] = Vector2(rect.position.x, rect.end.y)
visible = not rect.has_no_area()
func move_rect(move : Vector2) -> void:
_selected_rect.position += move
_clipped_rect.position += move
set_rect(_selected_rect)
func select_rect() -> void:
var undo_data = _get_undo_data(false)
Global.current_project.selected_rect = _selected_rect
commit_undo("Rectangle Select", undo_data)
func move_start(move_pixel : bool) -> void:
if not move_pixel:
return
_undo_data = _get_undo_data(true)
var project := Global.current_project
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
var rect = Rect2(Vector2.ZERO, project.size)
_clipped_rect = rect.clip(_selected_rect)
_move_image = image.get_rect(_clipped_rect)
_move_texture.create_from_image(_move_image, 0)
var size := _clipped_rect.size
rect = Rect2(Vector2.ZERO, size)
_clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST)
image.blit_rect(_clear_image, rect, _clipped_rect.position)
Global.canvas.update_texture(project.current_layer)
_move_pixel = true
update()
func move_end() -> void:
var undo_data = _undo_data if _move_pixel else _get_undo_data(false)
if _move_pixel:
var project := Global.current_project
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
var size := _clipped_rect.size
var rect = Rect2(Vector2.ZERO, size)
image.blit_rect_mask(_move_image, _move_image, rect, _clipped_rect.position)
_move_pixel = false
update()
Global.current_project.selected_rect = _selected_rect
commit_undo("Rectangle Select", undo_data)
_undo_data.clear()
func copy() -> void:
if _selected_rect.has_no_area():
return
var project := Global.current_project
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
_clipboard = image.get_rect(_selected_rect)
if _clipboard.is_invisible():
return
var brush = _clipboard.get_rect(_clipboard.get_used_rect())
project.brushes.append(brush)
Brushes.add_project_brush(brush)
func paste() -> void:
if _clipboard.get_size() <= Vector2.ZERO:
return
var undo_data = _get_undo_data(true)
var project := Global.current_project
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
var size := _selected_rect.size
var rect = Rect2(Vector2.ZERO, size)
image.blend_rect(_clipboard, rect, _selected_rect.position)
commit_undo("Draw", undo_data)
func delete() -> void:
var undo_data = _get_undo_data(true)
var project := Global.current_project
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
var size := _selected_rect.size
var rect = Rect2(Vector2.ZERO, size)
_clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST)
image.blit_rect(_clear_image, rect, _selected_rect.position)
commit_undo("Draw", undo_data)
func commit_undo(action : String, undo_data : Dictionary) -> void:
var redo_data = _get_undo_data("image_data" in undo_data)
var project := Global.current_project
project.undos += 1
project.undo_redo.create_action(action)
project.undo_redo.add_do_property(project, "selected_rect", redo_data["selected_rect"])
project.undo_redo.add_undo_property(project, "selected_rect", undo_data["selected_rect"])
if "image_data" in undo_data:
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
project.undo_redo.add_do_property(image, "data", redo_data["image_data"])
project.undo_redo.add_undo_property(image, "data", undo_data["image_data"])
project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer)
project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer)
project.undo_redo.commit_action()
func _get_undo_data(undo_image : bool) -> Dictionary:
var data = {}
var project := Global.current_project
data["selected_rect"] = Global.current_project.selected_rect
if undo_image:
var image : Image = project.frames[project.current_frame].cels[project.current_layer].image
image.unlock()
data["image_data"] = image.data
image.lock()
return data

130
src/Tools/Base.gd Normal file
View 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
View 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
View 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
View 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
View 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)

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

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

View file

@ -1,70 +1,29 @@
extends BaseButton extends BaseButton
export var brush_type := 0 # Global.Brush_Types.PIXEL var brush := Brushes.Brush.new()
export var custom_brush_index := -3
var random_brushes := []
func _on_BrushButton_pressed() -> void: func _on_BrushButton_pressed() -> void:
# Delete the brush on middle mouse press # Delete the brush on middle mouse press
if Input.is_action_just_released("middle_mouse"): if Input.is_action_just_released("middle_mouse"):
_on_DeleteButton_pressed() _on_DeleteButton_pressed()
return
# Change brush
Global.current_brush_types[Global.brush_type_window_position] = brush_type
Global.custom_brush_indexes[Global.brush_type_window_position] = custom_brush_index
if brush_type == Global.Brush_Types.FILE or brush_type == Global.Brush_Types.RANDOM_FILE or brush_type == Global.Brush_Types.CUSTOM:
if Global.current_tools[Global.brush_type_window_position] == Global.Tools.PENCIL:
Global.color_interpolation_containers[Global.brush_type_window_position].visible = true
else: else:
Global.color_interpolation_containers[Global.brush_type_window_position].visible = false Global.brushes_popup.select_brush(brush)
Global.update_custom_brush(Global.brush_type_window_position)
Global.brushes_popup.hide()
func _on_DeleteButton_pressed() -> void: func _on_DeleteButton_pressed() -> void:
if brush_type != Global.Brush_Types.CUSTOM: if brush.type != Brushes.CUSTOM:
return return
if Global.custom_brush_indexes[0] == custom_brush_index: Global.brushes_popup.remove_brush(self)
Global.custom_brush_indexes[0] = -3
Global.current_brush_types[0] = Global.Brush_Types.PIXEL
Global.update_custom_brush(0)
if Global.custom_brush_indexes[1] == custom_brush_index:
Global.custom_brush_indexes[1] = -3
Global.current_brush_types[1] = Global.Brush_Types.PIXEL
Global.update_custom_brush(1)
Global.current_project.undos += 1
Global.current_project.undo_redo.create_action("Delete Custom Brush")
for i in range(Global.project_brush_container.get_child_count()):
var bb = Global.project_brush_container.get_child(i)
if Global.custom_brush_indexes[0] == bb.custom_brush_index:
Global.custom_brush_indexes[0] -= 1
if Global.custom_brush_indexes[1] == bb.custom_brush_index:
Global.custom_brush_indexes[1] -= 1
Global.current_project.undo_redo.add_do_property(bb, "custom_brush_index", bb.custom_brush_index - 1)
Global.current_project.undo_redo.add_undo_property(bb, "custom_brush_index", bb.custom_brush_index)
var custom_brushes: Array = Global.current_project.brushes.duplicate()
custom_brushes.remove(custom_brush_index)
Global.current_project.undo_redo.add_do_property(Global.current_project, "brushes", custom_brushes)
Global.current_project.undo_redo.add_undo_property(Global.current_project, "brushes", Global.current_project.brushes)
Global.current_project.undo_redo.add_do_method(Global, "redo_custom_brush", self)
Global.current_project.undo_redo.add_undo_method(Global, "undo_custom_brush", self)
Global.current_project.undo_redo.commit_action()
func _on_BrushButton_mouse_entered() -> void: func _on_BrushButton_mouse_entered() -> void:
if brush_type == Global.Brush_Types.CUSTOM: if brush.type == Brushes.CUSTOM:
$DeleteButton.visible = true $DeleteButton.visible = true
func _on_BrushButton_mouse_exited() -> void: func _on_BrushButton_mouse_exited() -> void:
if brush_type == Global.Brush_Types.CUSTOM: if brush.type == Brushes.CUSTOM:
$DeleteButton.visible = false $DeleteButton.visible = false

132
src/UI/BrushesPopup.gd Normal file
View 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)

View file

@ -1,41 +1,12 @@
[gd_scene load_steps=6 format=2] [gd_scene load_steps=2 format=2]
[ext_resource path="res://src/UI/BrushButton.tscn" type="PackedScene" id=2] [ext_resource path="res://src/UI/BrushesPopup.gd" type="Script" id=1]
[sub_resource type="Image" id=5]
data = {
"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0 ),
"format": "LumAlpha8",
"height": 9,
"mipmaps": false,
"width": 9
}
[sub_resource type="ImageTexture" id=2]
flags = 3
flags = 3
image = SubResource( 5 )
size = Vector2( 9, 9 )
[sub_resource type="Image" id=6]
data = {
"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0 ),
"format": "LumAlpha8",
"height": 9,
"mipmaps": false,
"width": 9
}
[sub_resource type="ImageTexture" id=4]
flags = 3
flags = 3
image = SubResource( 6 )
size = Vector2( 9, 9 )
[node name="BrushesPopup" type="Popup"] [node name="BrushesPopup" type="Popup"]
margin_right = 226.0 margin_right = 226.0
margin_bottom = 144.0 margin_bottom = 144.0
rect_min_size = Vector2( 0, 144 ) rect_min_size = Vector2( 0, 144 )
script = ExtResource( 1 )
[node name="TabContainer" type="TabContainer" parent="."] [node name="TabContainer" type="TabContainer" parent="."]
anchor_right = 1.0 anchor_right = 1.0
@ -55,36 +26,8 @@ size_flags_horizontal = 3
scroll_horizontal_enabled = false scroll_horizontal_enabled = false
[node name="FileBrushContainer" type="GridContainer" parent="TabContainer/File"] [node name="FileBrushContainer" type="GridContainer" parent="TabContainer/File"]
margin_right = 104.0
margin_bottom = 32.0
columns = 6 columns = 6
[node name="PixelBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )]
hint_tooltip = "Pixel brush"
mouse_default_cursor_shape = 2
[node name="CircleBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )]
margin_left = 36.0
margin_right = 68.0
hint_tooltip = "Filled circle brush"
mouse_default_cursor_shape = 2
brush_type = 1
custom_brush_index = -2
[node name="BrushTexture" parent="TabContainer/File/FileBrushContainer/CircleBrushButton" index="0"]
texture = SubResource( 2 )
[node name="FilledCircleBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )]
margin_left = 72.0
margin_right = 104.0
hint_tooltip = "Circle brush"
mouse_default_cursor_shape = 2
brush_type = 2
custom_brush_index = -1
[node name="BrushTexture" parent="TabContainer/File/FileBrushContainer/FilledCircleBrushButton" index="0"]
texture = SubResource( 4 )
[node name="Project" type="ScrollContainer" parent="TabContainer"] [node name="Project" type="ScrollContainer" parent="TabContainer"]
visible = false visible = false
anchor_right = 1.0 anchor_right = 1.0
@ -99,7 +42,3 @@ scroll_horizontal_enabled = false
[node name="ProjectBrushContainer" type="GridContainer" parent="TabContainer/Project"] [node name="ProjectBrushContainer" type="GridContainer" parent="TabContainer/Project"]
columns = 5 columns = 5
[editable path="TabContainer/File/FileBrushContainer/CircleBrushButton"]
[editable path="TabContainer/File/FileBrushContainer/FilledCircleBrushButton"]

View file

@ -1,25 +1,23 @@
extends VBoxContainer extends VBoxContainer
var previous_colors := [Color.black, Color.white] onready var left_picker := $ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/LeftColorPickerButton
onready var right_picker := $ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/RightColorPickerButton
func _ready() -> void:
Tools.connect("color_changed", self, "update_color")
left_picker.get_picker().presets_visible = false
right_picker.get_picker().presets_visible = false
func _on_ColorSwitch_pressed() -> void: func _on_ColorSwitch_pressed() -> void:
var temp : Color = Global.color_pickers[0].color Tools.swap_color()
Global.color_pickers[0].color = Global.color_pickers[1].color
Global.color_pickers[1].color = temp
Global.update_custom_brush(0)
Global.update_custom_brush(1)
func _on_ColorPickerButton_color_changed(color : Color, right : bool): func _on_ColorPickerButton_color_changed(color : Color, right : bool):
var mouse_button := int(right) var button := BUTTON_RIGHT if right else BUTTON_LEFT
# If the color changed while it's on full transparency, make it opaque (GH issue #54) Tools.assign_color(color, button)
if color.a == 0:
if previous_colors[mouse_button].r != color.r or previous_colors[mouse_button].g != color.g or previous_colors[mouse_button].b != color.b:
Global.color_pickers[mouse_button].color.a = 1
Global.update_custom_brush(mouse_button)
previous_colors[mouse_button] = color
func _on_ColorPickerButton_pressed() -> void: func _on_ColorPickerButton_pressed() -> void:
@ -31,108 +29,11 @@ func _on_ColorPickerButton_popup_closed() -> void:
func _on_ColorDefaults_pressed() -> void: func _on_ColorDefaults_pressed() -> void:
Global.color_pickers[0].color = Color.black Tools.default_color()
Global.color_pickers[1].color = Color.white
Global.update_custom_brush(0)
Global.update_custom_brush(1)
func _on_FitToFrameButton_pressed() -> void: func update_color(color : Color, button : int) -> void:
Global.camera.fit_to_frame(Global.current_project.size) if button == BUTTON_LEFT:
left_picker.color = color
func _on_100ZoomButton_pressed() -> void:
Global.camera.zoom = Vector2.ONE
Global.camera.offset = Global.current_project.size / 2
Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %"
Global.horizontal_ruler.update()
Global.vertical_ruler.update()
func _on_BrushTypeButton_pressed(right : bool) -> void:
var mouse_button := int(right)
Global.brushes_popup.popup(Rect2(Global.brush_type_buttons[mouse_button].rect_global_position, Vector2(226, 72)))
Global.brush_type_window_position = mouse_button
func _on_BrushSizeEdit_value_changed(value : float, right : bool) -> void:
var mouse_button := int(right)
var new_size = int(value)
Global.brush_size_edits[mouse_button].value = value
Global.brush_size_sliders[mouse_button].value = value
Global.brush_sizes[mouse_button] = new_size
Global.update_custom_brush(mouse_button)
func _on_PixelPerfectMode_toggled(button_pressed : bool, right : bool) -> void:
var mouse_button := int(right)
Global.pixel_perfect[mouse_button] = button_pressed
func _on_InterpolateFactor_value_changed(value : float, right : bool) -> void:
var mouse_button := int(right)
Global.interpolate_spinboxes[mouse_button].value = value
Global.interpolate_sliders[mouse_button].value = value
Global.update_custom_brush(mouse_button)
func _on_FillAreaOptions_item_selected(ID : int, right : bool) -> void:
var mouse_button := int(right)
Global.fill_areas[mouse_button] = ID
func _on_FillWithOptions_item_selected(ID : int, right : bool) -> void:
var mouse_button := int(right)
Global.fill_with[mouse_button] = ID
if ID == 1:
Global.fill_pattern_containers[mouse_button].visible = true
else: else:
Global.fill_pattern_containers[mouse_button].visible = false right_picker.color = color
func _on_PatternTypeButton_pressed(right : bool) -> void:
var mouse_button := int(right)
Global.pattern_window_position = mouse_button
Global.patterns_popup.popup(Rect2(Global.brush_type_buttons[mouse_button].rect_global_position, Vector2(226, 72)))
func _on_PatternOffsetX_value_changed(value : float, right : bool) -> void:
var mouse_button := int(right)
Global.fill_pattern_offsets[mouse_button].x = value
func _on_PatternOffsetY_value_changed(value : float, right : bool) -> void:
var mouse_button := int(right)
Global.fill_pattern_offsets[mouse_button].y = value
func _on_LightenDarken_item_selected(ID : int, right : bool) -> void:
var mouse_button := int(right)
Global.ld_modes[mouse_button] = ID
func _on_LDAmount_value_changed(value : float, right : bool) -> void:
var mouse_button := int(right)
Global.ld_amounts[mouse_button] = value / 100
Global.ld_amount_sliders[mouse_button].value = value
Global.ld_amount_spinboxes[mouse_button].value = value
func _on_ForColorOptions_item_selected(ID : int, right : bool) -> void:
var mouse_button := int(right)
Global.color_picker_for[mouse_button] = ID
func _on_ZoomModeOptions_item_selected(ID : int, right : bool) -> void:
var mouse_button := int(right)
Global.zoom_modes[mouse_button] = ID
func _on_HorizontalMirroring_toggled(button_pressed : bool, right : bool) -> void:
var mouse_button := int(right)
Global.horizontal_mirror[mouse_button] = button_pressed
func _on_VerticalMirroring_toggled(button_pressed : bool, right : bool) -> void:
var mouse_button := int(right)
Global.vertical_mirror[mouse_button] = button_pressed

File diff suppressed because one or more lines are too long

View file

@ -1,22 +1,8 @@
extends TextureButton extends TextureButton
var image : Image var pattern := Patterns.Pattern.new()
var image_size : Vector2
var texture : ImageTexture
func _ready() -> void:
if image:
image_size = image.get_size()
texture = ImageTexture.new()
texture.create_from_image(image, 0)
func _on_PatternButton_pressed() -> void: func _on_PatternButton_pressed() -> void:
Global.pattern_images[Global.pattern_window_position] = image Global.patterns_popup.select_pattern(pattern)
Global.fill_pattern_containers[Global.pattern_window_position].get_child(0).get_child(0).texture = texture
Global.fill_pattern_containers[Global.pattern_window_position].get_child(2).get_child(1).max_value = image_size.x - 1
Global.fill_pattern_containers[Global.pattern_window_position].get_child(3).get_child(1).max_value = image_size.y - 1
Global.patterns_popup.hide()

45
src/UI/PatternsPopup.gd Normal file
View 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

View file

@ -1,8 +1,11 @@
[gd_scene format=2] [gd_scene load_steps=2 format=2]
[ext_resource path="res://src/UI/PatternsPopup.gd" type="Script" id=1]
[node name="PatternsPopup" type="PopupPanel"] [node name="PatternsPopup" type="PopupPanel"]
margin_right = 226.0 margin_right = 226.0
margin_bottom = 104.0 margin_bottom = 104.0
script = ExtResource( 1 )
__meta__ = { __meta__ = {
"_edit_use_anchors_": false "_edit_use_anchors_": false
} }

View file

@ -1,98 +1,40 @@
extends VBoxContainer extends VBoxContainer
var tools := [] # Node, shortcut
onready var tools := [
[$RectSelect, "rectangle_select"],
[$Zoom, "zoom"],
[$ColorPicker, "colorpicker"],
[$Pencil, "pencil"],
[$Eraser, "eraser"],
[$Bucket, "fill"],
[$LightenDarken, "lightdark"],
]
func _ready() -> void: func _ready() -> void:
# Node, left mouse shortcut, right mouse shortcut
tools.append([Global.find_node_by_name(self, "Pencil"), "left_pencil_tool", "right_pencil_tool"])
tools.append([Global.find_node_by_name(self, "Eraser"), "left_eraser_tool", "right_eraser_tool"])
tools.append([Global.find_node_by_name(self, "Bucket"), "left_fill_tool", "right_fill_tool"])
tools.append([Global.find_node_by_name(self, "LightenDarken"), "left_lightdark_tool", "right_lightdark_tool"])
tools.append([Global.find_node_by_name(self, "RectSelect"), "left_rectangle_select_tool", "right_rectangle_select_tool"])
tools.append([Global.find_node_by_name(self, "ColorPicker"), "left_colorpicker_tool", "right_colorpicker_tool"])
tools.append([Global.find_node_by_name(self, "Zoom"), "left_zoom_tool", "right_zoom_tool"])
for t in tools: for t in tools:
t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]]) t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]])
Global.update_hint_tooltips() Global.update_hint_tooltips()
func _input(event : InputEvent) -> void: func _input(event : InputEvent) -> void:
if Global.has_focus: if not Global.has_focus:
if event.is_action_pressed("undo") or event.is_action_pressed("redo") or event.is_action_pressed("redo_secondary"): return
for action in ["undo", "redo", "redo_secondary"]:
if event.is_action_pressed(action):
return return
for t in tools: # Handle tool shortcuts for t in tools: # Handle tool shortcuts
if event.is_action_pressed(t[2]): # Shortcut for right button (with Alt) if event.is_action_pressed("right_" + t[1] + "_tool"): # Shortcut for right button (with Alt)
_on_Tool_pressed(t[0], false, false) Tools.assign_tool(t[0].name, BUTTON_RIGHT)
elif event.is_action_pressed(t[1]): # Shortcut for left button elif event.is_action_pressed("left_" + t[1] + "_tool"): # Shortcut for left button
_on_Tool_pressed(t[0], false, true) Tools.assign_tool(t[0].name, BUTTON_LEFT)
func _on_Tool_pressed(tool_pressed : BaseButton, mouse_press := true, key_for_left := true) -> void: func _on_Tool_pressed(tool_pressed : BaseButton) -> void:
var current_action := tool_pressed.name var button := -1
var current_tool : int = Global.Tools.keys().find(current_action.to_upper()) button = BUTTON_LEFT if Input.is_action_just_released("left_mouse") else button
var left_tool_name := str(Global.Tools.keys()[Global.current_tools[0]]).to_lower() button = BUTTON_RIGHT if Input.is_action_just_released("right_mouse") else button
var right_tool_name := str(Global.Tools.keys()[Global.current_tools[1]]).to_lower() if button != -1:
var current_mouse_button := -1 Tools.assign_tool(tool_pressed.name, button)
if (mouse_press and Input.is_action_just_released("left_mouse")) or (!mouse_press and key_for_left):
left_tool_name = current_action.to_lower()
current_mouse_button = Global.Mouse_Button.LEFT
elif (mouse_press and Input.is_action_just_released("right_mouse")) or (!mouse_press and !key_for_left):
right_tool_name = current_action.to_lower()
current_mouse_button = Global.Mouse_Button.RIGHT
if current_mouse_button != -1:
Global.current_tools[current_mouse_button] = current_tool
# Start from 1, so the label won't get invisible
for i in range(1, Global.tool_options_containers[current_mouse_button].get_child_count()):
Global.tool_options_containers[current_mouse_button].get_child(i).visible = false
Global.tool_options_containers[current_mouse_button].get_node("EmptySpacer").visible = true
# Tool options visible depending on the selected tool
if current_tool == Global.Tools.PENCIL:
Global.brush_type_containers[current_mouse_button].visible = true
Global.brush_size_sliders[current_mouse_button].visible = true
Global.pixel_perfect_containers[current_mouse_button].visible = true
Global.mirror_containers[current_mouse_button].visible = true
if Global.current_brush_types[current_mouse_button] == Global.Brush_Types.FILE or Global.current_brush_types[current_mouse_button] == Global.Brush_Types.CUSTOM or Global.current_brush_types[current_mouse_button] == Global.Brush_Types.RANDOM_FILE:
Global.color_interpolation_containers[current_mouse_button].visible = true
elif current_tool == Global.Tools.ERASER:
Global.brush_type_containers[current_mouse_button].visible = true
Global.brush_size_sliders[current_mouse_button].visible = true
Global.pixel_perfect_containers[current_mouse_button].visible = true
Global.mirror_containers[current_mouse_button].visible = true
elif current_tool == Global.Tools.BUCKET:
Global.fill_area_containers[current_mouse_button].visible = true
Global.mirror_containers[current_mouse_button].visible = true
elif current_tool == Global.Tools.LIGHTENDARKEN:
Global.brush_type_containers[current_mouse_button].visible = true
Global.brush_size_sliders[current_mouse_button].visible = true
Global.pixel_perfect_containers[current_mouse_button].visible = true
Global.ld_containers[current_mouse_button].visible = true
Global.mirror_containers[current_mouse_button].visible = true
elif current_tool == Global.Tools.COLORPICKER:
Global.colorpicker_containers[current_mouse_button].visible = true
elif current_tool == Global.Tools.ZOOM:
Global.zoom_containers[current_mouse_button].visible = true
for t in tools:
var tool_name : String = t[0].name.to_lower()
var texture_button : TextureRect = t[0].get_child(0)
if tool_name == left_tool_name and tool_name == right_tool_name:
Global.change_button_texturerect(texture_button, "%s_l_r.png" % tool_name.to_lower())
elif tool_name == left_tool_name:
Global.change_button_texturerect(texture_button, "%s_l.png" % tool_name.to_lower())
elif tool_name == right_tool_name:
Global.change_button_texturerect(texture_button, "%s_r.png" % tool_name.to_lower())
else:
Global.change_button_texturerect(texture_button, "%s.png" % tool_name.to_lower())
Global.left_cursor_tool_texture.create_from_image(load("res://assets/graphics/cursor_icons/%s_cursor.png" % left_tool_name), 0)
Global.right_cursor_tool_texture.create_from_image(load("res://assets/graphics/cursor_icons/%s_cursor.png" % right_tool_name), 0)

View file

@ -39,6 +39,9 @@ func setup_edit_menu() -> void:
var edit_menu_items := { var edit_menu_items := {
"Undo" : InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(), "Undo" : InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(),
"Redo" : InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(), "Redo" : InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(),
"Copy" : InputMap.get_action_list("copy")[0].get_scancode_with_modifiers(),
"Paste" : InputMap.get_action_list("paste")[0].get_scancode_with_modifiers(),
"Delete" : InputMap.get_action_list("delete")[0].get_scancode_with_modifiers(),
"Clear Selection" : 0, "Clear Selection" : 0,
"Preferences" : 0 "Preferences" : 0
} }
@ -201,15 +204,16 @@ func edit_menu_id_pressed(id : int) -> void:
Global.control.redone = true Global.control.redone = true
Global.current_project.undo_redo.redo() Global.current_project.undo_redo.redo()
Global.control.redone = false Global.control.redone = false
2: # Clear selection 2: # Copy
Global.canvas.handle_undo("Rectangle Select") Global.selection_rectangle.copy()
Global.selection_rectangle.polygon[0] = Vector2.ZERO 3: # paste
Global.selection_rectangle.polygon[1] = Vector2.ZERO Global.selection_rectangle.paste()
Global.selection_rectangle.polygon[2] = Vector2.ZERO 4: # Delete
Global.selection_rectangle.polygon[3] = Vector2.ZERO Global.selection_rectangle.delete()
Global.current_project.selected_pixels.clear() 5: # Clear selection
Global.canvas.handle_redo("Rectangle Select") Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0))
3: # Preferences Global.selection_rectangle.select_rect()
6: # Preferences
Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.preferences_dialog.popup_centered(Vector2(400, 280))
Global.dialog_open(true) Global.dialog_open(true)