simple composable grid context and grid controller, fiend sprite, godotxel addon

This commit is contained in:
veclav talica 2023-11-24 04:02:18 +05:00
commit 9441aa63cb
180 changed files with 9040 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.png filter=lfs diff=lfs merge=lfs -text

1
.import/.gdignore Normal file
View File

@ -0,0 +1 @@

View File

@ -0,0 +1,3 @@
source_md5="02600b8740456771affee06983e57ba2"
dest_md5="da049aced7d51eead426dccecd83e17d"

View File

@ -0,0 +1,3 @@
source_md5="fdff01762759c532cf06f6c399898003"
dest_md5="fe9b15be81847e11ef7929564db675ca"

View File

@ -0,0 +1,3 @@
source_md5="3364be2e9af88c9893e2a69af65b0547"
dest_md5="e754226789026560da0e12e720b31660"

View File

@ -0,0 +1,3 @@
source_md5="c767c1f5f8db79fd5b9edf9508fc4d1b"
dest_md5="14567b18c33c4cd5dee7d4b11f07b7f2"

View File

@ -0,0 +1,3 @@
source_md5="0ec1c2f2a7018aa4122bf76285f87443"
dest_md5="955c166c2c50a7fc7bb522de89df91ce"

View File

@ -0,0 +1,3 @@
source_md5="b2f8bc01566c3fa839501355155541c6"
dest_md5="d10f6c9b72dba11f0356fa63b0efbddd"

View File

@ -0,0 +1,3 @@
source_md5="3e197fa301ef03f312365bd01429d08b"
dest_md5="0913df5f9c2a29dd74c7b88f6a7f37e3"

View File

@ -0,0 +1,3 @@
source_md5="9d4fb491eb4bf3869fca7d038c928759"
dest_md5="9ebfff15dc061a68c6bf82d1cb83e6af"

View File

@ -0,0 +1,3 @@
source_md5="e1db1064c72265a01a992e8d77615cd0"
dest_md5="8de5f0178bb0f6b0773e245a52d2df9a"

View File

@ -0,0 +1,3 @@
source_md5="5a6b6ab9dca02fb1f80c7468b43a982c"
dest_md5="e5448547844aef6e50771770909354c4"

View File

@ -0,0 +1,3 @@
source_md5="8a10fc33b613b33b4d51afda12ea1d74"
dest_md5="22b5b124b8176533aa5cd11928ff8e1a"

View File

@ -0,0 +1,3 @@
source_md5="9e8801de4bef4983cca7e993585bb944"
dest_md5="75483eb7190f06ec5b7c17b158dff864"

View File

@ -0,0 +1,3 @@
source_md5="f4c7f3ffcadcda366aedce818d6efbfe"
dest_md5="e02525cb50c283e4e58be85a22e82e3a"

View File

@ -0,0 +1,3 @@
source_md5="7ba250dc0369bd9ea174a98fa96fd159"
dest_md5="56c8662891cd27a8926447a80462218e"

View File

@ -0,0 +1,3 @@
source_md5="2117353e2e3a97b4461460d303806021"
dest_md5="1e89a6da5bb0a41a9f96eed98d099ecb"

View File

@ -0,0 +1,3 @@
source_md5="7c1193f6500d1fc0411cdeb3000c36df"
dest_md5="81ad6cf73db3ebed1cf028a8ba220086"

View File

@ -0,0 +1,3 @@
source_md5="854d565fe87c02a40a91d093e1942111"
dest_md5="ad1a81cfcbcbdb6d5a9f99dd98a9e5e0"

View File

@ -0,0 +1,3 @@
source_md5="5e9bc42ebbb41300af1c572f986960ac"
dest_md5="9761c0d50600309e43d1d28fd72b54f5"

Binary file not shown.

View File

@ -0,0 +1,3 @@
source_md5="47313fa4c47a9963fddd764e1ec6e4a8"
dest_md5="26ea799ea0a3da9e753b3ebe822e0570"

Binary file not shown.

View File

@ -0,0 +1,3 @@
source_md5="1f208aa17bb746f72d0c708960e161b8"
dest_md5="9a39a80a2cbcca6412c335d0d192b71e"

View File

@ -0,0 +1,3 @@
source_md5="f5c365385f1229bba28c2118b09557f0"
dest_md5="215730718d238613f5ecfb60c77fd1c3"

View File

@ -0,0 +1,3 @@
source_md5="e0848eef5591cbefcdd580e2266df32a"
dest_md5="7e23ebf9e94e4bc4758723749616c112"

View File

@ -0,0 +1,3 @@
source_md5="ed7ea9ed6750cf2fbe5141c6745f1a80"
dest_md5="365e095f05917944fca1b11c61acdd5b"

View File

@ -0,0 +1,3 @@
source_md5="14b94da08f95d2ae578c2be11f83ccd8"
dest_md5="ece14a0ab6aa8577445cdcf27ed7ac33"

View File

@ -0,0 +1,24 @@
extends Node
class_name GEAnimation
var frames = []
var anim_idx = -1
func _ready():
pass
func add_frame(frame):
frames.append(frame)
func get_anim_index():
return anim_idx
func set_anim_index(index):
anim_idx = index

View File

@ -0,0 +1,106 @@
class_name BrushPrefabs
const list = [
[ Vector2(0, -1),
Vector2(-1, 0), Vector2(0, 0), Vector2(1, 0),
Vector2(0, 1)
],
[Vector2(-1, -1), Vector2(0, -1), Vector2(1, -1),
Vector2(-1, 0), Vector2(0, 0), Vector2(1, 0),
Vector2(-1, 1), Vector2(0, 1), Vector2(1, 1),
],
[
Vector2(-1, 0), Vector2(0, 0), Vector2(1, 0),
],
[ Vector2(0, -1),
Vector2(0, 0),
Vector2(0, 1)
]
]
enum Type {
V_LINE,
H_LINE,
RECT,
CIRCLE,
}
static func get_brush(type, size: int):
var pixels = []
if size < 1:
size = 1
match type:
Type.CIRCLE:
size += 1
var center = Vector2.ZERO
var last = center
var radius = size / 2.0
for x in range(size):
for y in range(size):
if Vector2(x - radius, y - radius).length() < size / 3.0:
pixels.append(Vector2(x, y))
var avg = Vector2(size / 2, size / 2)
avg = Vector2(floor(avg.x), floor(avg.y))
for i in range(pixels.size()):
pixels[i] -= avg
Type.RECT:
var center = Vector2.ZERO
var last = center
for x in range(size):
for y in range(size):
pixels.append(Vector2(x, y))
var avg = Vector2.ZERO
for cell in pixels:
avg += cell
avg.x /= pixels.size()
avg.y /= pixels.size()
avg = Vector2(floor(avg.x), floor(avg.y))
for i in range(pixels.size()):
pixels[i] -= avg
Type.V_LINE:
var center = Vector2.ZERO
var last = center
pixels.append(Vector2.ZERO)
for i in range(size - 1):
var sig = sign(last.y)
if sig == 0:
sig = 1
if last.y < 0:
center.y = abs(last.y) * -sig
else:
center.y = abs(last.y+1) * -sig
last = center
pixels.append(center)
Type.H_LINE:
var center = Vector2.ZERO
var last = center
pixels.append(Vector2.ZERO)
for i in range(size - 1):
var sig = sign(last.x)
if sig == 0:
sig = 1
if last.x < 0:
center.x = abs(last.x) * -sig
else:
center.x = abs(last.x+1) * -sig
last = center
pixels.append(center)
return pixels

517
addons/Godoxel/Canvas.gd Normal file
View File

@ -0,0 +1,517 @@
extends Control
class_name GECanvas
tool
export var pixel_size: float = 16 setget set_pixel_size
export(int, 1, 2500) var canvas_width = 48 setget set_canvas_width # == pixels
export(int, 1, 2500) var canvas_height = 28 setget set_canvas_height # == pixels
export var grid_size = 16 setget set_grid_size
export var big_grid_size = 10 setget set_big_grid_size
export var can_draw = true
var mouse_in_region
var mouse_on_top
var frame: GEFrame
#var layers : Array = [] # Key: layer_name, val: GELayer
var active_layer: GELayer
var preview_layer: GELayer
var tool_layer: GELayer
var canvas_layers: Control
var canvas
var grid
var big_grid
var selected_pixels = []
var symmetry_x = false
var symmetry_y = false
func _ready():
#-------------------------------
# Set nodes
#-------------------------------
canvas = find_node("Canvas")
grid = find_node("Grid")
big_grid = find_node("BigGrid")
canvas_layers = find_node("CanvasLayers")
#-------------------------------
# setup layers and canvas
#-------------------------------
if not is_connected("mouse_entered", self, "_on_mouse_entered"):
connect("mouse_entered", self, "_on_mouse_entered")
if not is_connected("mouse_exited", self, "_on_mouse_exited"):
connect("mouse_exited", self, "_on_mouse_exited")
#-------------------------------
# setup layers and canvas
#-------------------------------
#canvas_size = Vector2(int(rect_size.x / grid_size), int(rect_size.y / grid_size))
#pixel_size = canvas_size
create_preview_layer()
create_tool_layer()
set_process(true)
func _process(delta):
if not is_visible_in_tree():
return
var mouse_position = get_local_mouse_position()
var rect = Rect2(Vector2(0, 0), rect_size)
mouse_in_region = rect.has_point(mouse_position)
func _draw():
if preview_layer:
preview_layer.update_texture()
if tool_layer:
tool_layer.update_texture()
# for layer in layers:
# layer.update_texture()
# TODO
#if frame:
#frame._draw()
func resize(width: int, height: int):
if width < 0:
width = 1
if height < 0:
height = 1
# TODO move resize to editor.gd -> for all frames
set_canvas_width(width)
set_canvas_height(height)
preview_layer.resize(width, height)
tool_layer.resize(width, height)
#frame.resize(width, height)
func set_frame(new_frame):
frame = new_frame
if canvas_layers.get_child_count() > 0:
canvas_layers.remove_child(canvas_layers.get_child(0))
canvas_layers.add_child(frame)
frame.width = canvas_width
frame.height = canvas_height
frame.anchor_right = 1
frame.anchor_bottom = 1
frame.margin_right = 0
frame.margin_bottom = 0
if not frame.layers.empty():
active_layer = frame.layers[owner.current_layer_idx]
################################################################
# Export
################################################################
func get_current_frame_image() -> Image:
var image = Image.new()
image.create(canvas_width, canvas_height, true, Image.FORMAT_RGBA8)
image.lock()
image.fill(Color.transparent)
image.unlock()
image.lock()
for layer in frame.layers:
if not layer.visible:
continue
for x in range(frame.width):
for y in range(frame.height):
var color = layer.get_pixel(x, y)
var image_color = image.get_pixel(x, y)
if color.a != 0:
image.set_pixel(x, y, color)
else:
image.set_pixel(x, y, image_color.blend(color))
image.unlock()
return image
func get_current_layer_image() -> Image:
var image = Image.new()
image.create(canvas_width, canvas_height, true, Image.FORMAT_RGBA8)
image.lock()
image.fill(Color.transparent)
image.unlock()
image.lock()
for layer in frame.layers:
if layer != active_layer:
continue
for x in range(frame.width):
for y in range(frame.height):
var color = layer.get_pixel(x, y)
var image_color = image.get_pixel(x, y)
if color.a != 0:
image.set_pixel(x, y, color)
else:
image.set_pixel(x, y, image_color.blend(color))
image.unlock()
return image
################################################################
# Pixel/Grid size
################################################################
func set_pixel_size(size: float):
pixel_size = size
set_grid_size(grid_size)
set_big_grid_size(big_grid_size)
set_canvas_width(canvas_width)
set_canvas_height(canvas_height)
func set_grid_size(size):
grid_size = size
if not find_node("Grid"):
return
find_node("Grid").size = size * pixel_size
func set_big_grid_size(size):
big_grid_size = size
if not find_node("BigGrid"):
return
find_node("BigGrid").size = size * pixel_size
func set_canvas_width(val: int):
canvas_width = val
rect_size.x = canvas_width * pixel_size
func set_canvas_height(val: int):
canvas_height = val
rect_size.y = canvas_height * pixel_size
#-------------------------------
# Layer
#-------------------------------
func toggle_alpha_locked(layer_name: String):
var layer = find_layer_by_name(layer_name)
layer.toggle_alpha_locked()
func is_alpha_locked() -> bool:
return active_layer.alpha_locked
func get_content_margin() -> Rect2:
return frame.get_content_margin()
func crop_to_content():
frame.crop_to_content()
func get_active_layer() -> GELayer:
return active_layer
func get_preview_layer():
return preview_layer
func clear_active_layer():
active_layer.clear()
func clear_preview_layer():
preview_layer.clear()
func clear_layer(layer_name: String):
for layer in frame.layers:
if layer.name == layer_name:
layer.clear()
break
func remove_layer(layer_name: String):
# change current layer if the active layer is removed
var del_layer = find_layer_by_name(layer_name)
del_layer.clear()
if del_layer == active_layer:
for layer in frame.layers:
if layer == preview_layer or layer == active_layer or layer == tool_layer:
continue
active_layer = layer
print("Select active layer: ", active_layer)
break
frame.layers.erase(del_layer)
return active_layer
func create_preview_layer():
var layer = GELayer.new()
layer.create($PreviewLayer, canvas_width, canvas_height)
preview_layer = layer
return layer
func create_tool_layer():
var layer = GELayer.new()
layer.create($ToolPreviewLayer, canvas_width, canvas_height)
tool_layer = layer
return layer
func duplicate_layer(layer: GELayer):
for existing_layer in frame.layers:
if layer.name == existing_layer.name:
return
var new_layer: GELayer = GELayer.new()
new_layer.image.copy_from(layer.image)
return new_layer
func toggle_layer_visibility(layer_name: String):
var layer_idx = get_layer_index(layer_name)
assert(layer_idx != -1, "Layer name not found!")
var layer = frame.layers[layer_idx]
layer.visible = not layer.visible
return layer.visible
func get_layer_index(layer_name: String):
var idx = 0
for layer in frame.layers:
if layer.name == layer_name:
return idx
idx += 1
return -1
func find_layer_by_name(layer_name: String):
for layer in frame.layers:
if layer.name == layer_name:
return layer
return null
func toggle_lock_layer(layer_name: String):
find_layer_by_name(layer_name).toggle_lock()
func is_active_layer_locked() -> bool:
return active_layer.locked
func get_active_layer_index() -> int:
return get_layer_index(active_layer.name)
func move_layer_forward(layer_name: String):
var layer = find_layer_by_name(layer_name).texture_rect_ref
var new_idx = max(layer.get_index() - 1, 0)
layer.get_parent().move_child(layer, new_idx)
func move_layer_back(layer_name: String):
var layer = find_layer_by_name(layer_name).texture_rect_ref
layer.get_parent().move_child(layer, layer.get_index() + 1)
func select_layer(layer: GELayer):
active_layer = layer
#-------------------------------
# Check
#-------------------------------
func _on_mouse_entered():
mouse_on_top = true
func _on_mouse_exited():
mouse_on_top = false
func is_inside_canvas(x, y):
if x < 0 or y < 0:
return false
if x >= canvas_width or y >= canvas_height:
return false
return true
#-------------------------------
# Basic pixel-layer options
#-------------------------------
#Note: Arrays are always passed by reference. To get a copy of an array which
# can be modified independently of the original array, use duplicate.
# (https://docs.godotengine.org/en/stable/classes/class_array.html)
func set_pixel_arr(pixels: Array, color: Color):
for pixel in pixels:
_set_pixel(active_layer, pixel.x, pixel.y, color)
func set_pixel_v(pos: Vector2, color: Color):
set_pixel(pos.x, pos.y, color)
func set_pixel(x: int, y: int, color: Color):
_set_pixel(active_layer, x, y, color)
func _set_pixel_v(layer: GELayer, v: Vector2, color: Color):
_set_pixel(layer, v.x, v.y, color)
func _set_pixel(layer: GELayer, x: int, y: int, color: Color):
if not is_inside_canvas(x, y):
return
layer.set_pixel(x, y, color)
func get_pixel_v(pos: Vector2):
return get_pixel(pos.x, pos.y)
func get_pixel(x: int, y: int):
if active_layer:
return active_layer.get_pixel(x, y)
return null
func set_preview_pixel_v(pos: Vector2, color: Color):
set_preview_pixel(pos.x, pos.y, color)
func set_preview_pixel(x:int, y: int, color: Color):
if not is_inside_canvas(x, y):
return
preview_layer.set_pixel(x, y, color)
func get_preview_pixel_v(pos: Vector2):
return get_preview_pixel(pos.x, pos.y)
func get_preview_pixel(x: int, y: int):
if not preview_layer:
return null
return preview_layer.get_pixel(x, y)
#-------------------------------
# Grid
#-------------------------------
func toggle_grid():
$Grid.visible = not $Grid.visible
func show_grid():
$Grid.show()
func hide_grid():
$Grid.hide()
#-------------------------------
# Handy tools
#-------------------------------
func select_color(x, y):
print("???")
var same_color_pixels = []
var color = get_pixel(x, y)
for x in range(active_layer.layer_width):
for y in range(active_layer.layer_height):
var pixel_color = active_layer.get_pixel(x, y)
if pixel_color == color:
same_color_pixels.append(color)
return same_color_pixels
func select_same_color(x, y):
return get_neighbouring_pixels(x, y)
# returns array of Vector2
# yoinked from
# https://www.geeksforgeeks.org/flood-fill-algorithm-implement-fill-paint/
func get_neighbouring_pixels(pos_x: int, pos_y: int) -> Array:
var pixels = []
var to_check_queue = []
var checked_queue = []
to_check_queue.append(GEUtils.to_1D(pos_x, pos_y, canvas_width))
var color = get_pixel(pos_x, pos_y)
while not to_check_queue.empty():
var idx = to_check_queue.pop_front()
var p = GEUtils.to_2D(idx, canvas_width)
if idx in checked_queue:
continue
checked_queue.append(idx)
if get_pixel(p.x, p.y) != color:
continue
# add to result
pixels.append(p)
# check neighbours
var x = p.x - 1
var y = p.y
if is_inside_canvas(x, y):
idx = GEUtils.to_1D(x, y, canvas_width)
to_check_queue.append(idx)
x = p.x + 1
if is_inside_canvas(x, y):
idx = GEUtils.to_1D(x, y, canvas_width)
to_check_queue.append(idx)
x = p.x
y = p.y - 1
if is_inside_canvas(x, y):
idx = GEUtils.to_1D(x, y, canvas_width)
to_check_queue.append(idx)
y = p.y + 1
if is_inside_canvas(x, y):
idx = GEUtils.to_1D(x, y, canvas_width)
to_check_queue.append(idx)
return pixels

View File

@ -0,0 +1,31 @@
tool
extends Control
export var color = Color()
func _ready():
pass
func _draw():
var size = get_parent().rect_size
var pos = Vector2.ZERO #get_parent().rect_global_position
draw_outline_box(pos, size, color, 1)
func draw_outline_box(pos, size, color, width):
#Top line
draw_line(pos+Vector2(-1, 0), pos + Vector2(size.x, 0), color, width)
#Left line
draw_line(pos, pos + Vector2(0, size.y), color, width)
#Bottom line
draw_line(pos + Vector2(0, size.y), pos + Vector2(size.x, size.y), color, width)
#Right line
draw_line(pos + Vector2(size.x, 0), pos + Vector2(size.x, size.y), color, width)
func _process(delta):
if not is_visible_in_tree():
return
update()

34
addons/Godoxel/Colors.gd Normal file
View File

@ -0,0 +1,34 @@
tool
extends GridContainer
signal color_change_request
func _enter_tree():
for child in get_children():
child.set("custom_styles/normal", StyleBoxFlat.new())
child.get("custom_styles/normal").set("bg_color", Color(randf(), randf(), randf()))
for child in get_children():
if child.is_connected("pressed", self, "change_color_to"):
return
child.connect("pressed", self, "change_color_to", [child.get("custom_styles/normal").bg_color])
func change_color_to(color):
emit_signal("color_change_request", color)
func add_color_prefab(color: Color):
var dup = get_child(0).duplicate()
add_child(dup)
move_child(dup, 0)
dup.set("custom_styles/normal", StyleBoxFlat.new())
dup.get("custom_styles/normal").set("bg_color", color)
for child in get_children():
if child.is_connected("pressed", self, "change_color_to"):
return
child.connect("pressed", self, "change_color_to", [child.get("custom_styles/normal").bg_color])

View File

@ -0,0 +1,9 @@
extends RichTextLabel
tool
func _ready():
pass
func display_text(text):
self.text = text

1577
addons/Godoxel/Editor.gd Normal file

File diff suppressed because it is too large Load Diff

1309
addons/Godoxel/Editor.tscn Normal file

File diff suppressed because one or more lines are too long

107
addons/Godoxel/Frame.gd Normal file
View File

@ -0,0 +1,107 @@
extends Control
class_name GEFrame
tool
var layers = []
var width: int
var height: int
var preview_texture: ImageTexture = ImageTexture.new()
var preview_dirty = false
var preview_updated = true
func _ready():
pass
func _draw():
# return
# if not preview_dirty:
# return
# preview_dirty = false
for layer in layers:
layer.update_texture()
#_update_preview()
func _update_preview():
var image = Image.new()
image.create(width, height, true, Image.FORMAT_RGBA8)
image.lock()
image.fill(Color.transparent)
image.unlock()
image.lock()
for layer in layers:
if not layer.visible:
continue
for x in range(width):
for y in range(height):
var color = layer.get_pixel(x, y)
var image_color = image.get_pixel(x, y)
if color.a != 0:
image.set_pixel(x, y, color)
else:
image.set_pixel(x, y, image_color.blend(color))
image.unlock()
preview_texture.create_from_image(image)
preview_updated = true
func get_preview_texture():
return preview_texture
func set_layers(new_layers: Array):
for layer in new_layers:
add_frame_layer(layer)
func add_frame_layer(layer: GELayer):
layers.append(layer)
add_child(layer.texture_rect_ref, true)
return layer
func resize(width: int, height: int):
self.width = width
self.height = height
for layer in layers:
layer.resize(width, height)
func get_content_margin() -> Rect2:
var rect = Rect2(999999, 999999, -999999, -999999)
for layer in layers:
var r = layer.image.get_used_rect()
if r.position.x < rect.position.x:
rect.position.x = r.position.x
if r.position.y < rect.position.y:
rect.position.y = r.position.y
if r.size.x > rect.size.x:
rect.size.x = r.size.x
if r.size.y > rect.size.y:
rect.size.y = r.size.y
return rect
func crop_to_content():
var rect = get_content_margin()
#print(rect)
for layer in layers:
layer.image
# set_canvas_width(rect.size.x)
# set_canvas_height(rect.size.x)
# preview_layer.resize(width, height)
# tool_layer.resize(width, height)
# for layer in layers:
# layer.resize(width, height)

22
addons/Godoxel/LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2019 Flairieve
Copyright (c) 2020 cobrapitz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

107
addons/Godoxel/Layer.gd Normal file
View File

@ -0,0 +1,107 @@
extends Reference
class_name GELayer
tool
var name
var layer_width
var layer_height
var visible = true setget set_visible
var locked = false
var alpha_locked = false
var texture: ImageTexture
var image: Image
var texture_rect_ref
func _init():
texture = ImageTexture.new()
func create(texture_rect_ref, width: int, height: int):
self.texture_rect_ref = texture_rect_ref
assert(width > 0 and height > 0, "Texture size invalid!")
layer_width = width
layer_height = height
image = Image.new()
image.create(width, height, false, Image.FORMAT_RGBA8)
image.fill(Color.transparent)
update_texture()
func resize(width: int, height: int):
var pixel_colors = []
var prev_width = layer_width
var prev_height = layer_height
image.lock()
for y in range(prev_height):
for x in range(prev_width):
pixel_colors.append(image.get_pixel(x, y))
image.unlock()
layer_width = width
layer_height = height
image.create(width, height, false, Image.FORMAT_RGBA8)
image.fill(Color.transparent)
image.lock()
for x in range(prev_width):
for y in range(prev_height):
if x >= width or y >= height:
continue
image.set_pixel(x, y, pixel_colors[GEUtils.to_1D(x, y, prev_width)])
image.unlock()
update_texture()
func set_pixel(x, y, color):
image.lock()
image.set_pixel(x, y, color)
image.unlock()
func get_pixel(x: int, y: int):
if x < 0 or y < 0 or x >= image.get_width() or y >= image.get_height():
return null
image.lock()
var pixel = image.get_pixel(x, y)
image.unlock()
return pixel
func clear():
image.fill(Color.transparent)
update_texture()
func update_texture():
texture.create_from_image(image, 0)
texture_rect_ref.texture = texture
texture_rect_ref.margin_right = 0
texture_rect_ref.margin_bottom = 0
func set_visible(vis: bool):
# TODO upate frame preview when hide/show
visible = vis
texture_rect_ref.visible = visible
func toggle_lock():
locked = not locked
func toggle_alpha_locked():
alpha_locked = not alpha_locked
func copy_from(other: GELayer):
image.copy_from(other.image)

View File

@ -0,0 +1,108 @@
[gd_scene load_steps=11 format=2]
[ext_resource path="res://addons/Godoxel/assets/minidotta_invis.png" type="Texture" id=1]
[ext_resource path="res://addons/Godoxel/assets/minidotta.png" type="Texture" id=2]
[ext_resource path="res://addons/Godoxel/assets/arrow_down.png" type="Texture" id=3]
[ext_resource path="res://addons/Godoxel/assets/arrow_up.png" type="Texture" id=4]
[ext_resource path="res://addons/Godoxel/assets/lock_layer_1.png" type="Texture" id=5]
[ext_resource path="res://addons/Godoxel/assets/unlock_layer.png" type="Texture" id=6]
[sub_resource type="StyleBoxFlat" id=4]
resource_local_to_scene = true
bg_color = Color( 0.354706, 0.497302, 0.769531, 1 )
[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0.25098, 0.25098, 0.25098, 0 )
[sub_resource type="StyleBoxFlat" id=2]
bg_color = Color( 0.6, 0.6, 0.6, 0 )
[sub_resource type="StyleBoxFlat" id=3]
bg_color = Color( 0.6, 0.6, 0.6, 0 )
[node name="Layer1" type="Panel"]
show_behind_parent = true
anchor_right = 0.113281
anchor_bottom = 0.0416667
margin_bottom = -1.90735e-06
rect_min_size = Vector2( 0, 32 )
mouse_filter = 2
custom_styles/panel = SubResource( 4 )
__meta__ = {
"_edit_use_anchors_": true
}
[node name="Select" type="Button" parent="." groups=["layer_button"]]
anchor_right = 0.827586
anchor_bottom = 1.0
custom_styles/hover = SubResource( 1 )
custom_styles/pressed = SubResource( 1 )
custom_styles/focus = SubResource( 1 )
custom_styles/disabled = SubResource( 1 )
custom_styles/normal = SubResource( 1 )
text = "Layer 1"
align = 2
__meta__ = {
"_edit_use_anchors_": true
}
[node name="Visible" type="CheckButton" parent="."]
anchor_top = 0.5
anchor_bottom = 0.5
margin_left = 3.0
margin_top = -8.5
margin_right = 19.0
margin_bottom = 7.5
custom_icons/off = ExtResource( 1 )
custom_icons/on = ExtResource( 2 )
custom_styles/normal = SubResource( 2 )
pressed = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Lock" type="CheckButton" parent="."]
anchor_top = 0.5
anchor_bottom = 0.5
margin_left = 22.0
margin_top = -11.0
margin_right = 46.0
margin_bottom = 11.0
custom_icons/off = ExtResource( 6 )
custom_icons/on = ExtResource( 5 )
custom_styles/normal = SubResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchor_left = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -20.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Up" type="TextureButton" parent="VBoxContainer"]
margin_right = 20.0
margin_bottom = 14.0
rect_min_size = Vector2( 20, 0 )
size_flags_horizontal = 3
size_flags_vertical = 3
texture_normal = ExtResource( 4 )
texture_pressed = ExtResource( 2 )
expand = true
stretch_mode = 3
[node name="Down" type="TextureButton" parent="VBoxContainer"]
margin_top = 18.0
margin_right = 20.0
margin_bottom = 32.0
rect_min_size = Vector2( 20, 0 )
size_flags_horizontal = 3
size_flags_vertical = 3
texture_normal = ExtResource( 3 )
texture_pressed = ExtResource( 2 )
expand = true
stretch_mode = 3

View File

@ -0,0 +1,13 @@
tool
extends MenuButton
var popup = get_popup()
signal item_pressed
func _ready():
popup.connect("id_pressed", self, "id_pressed")
func id_pressed(id):
emit_signal("item_pressed", name, popup.get_item_text(id), id)

139
addons/Godoxel/Navbar.gd Normal file
View File

@ -0,0 +1,139 @@
tool
extends Control
var editor
var paint_canvas
func _ready():
editor = owner
paint_canvas = editor.find_node("PaintCanvas")
for i in get_node("Buttons").get_children():
i.connect("item_pressed", self, "button_pressed")
func button_pressed(button_name, button_item, id):
# print("pressed: ", button_name)
# print("pressed item is: '%s'" % button_item)
match button_name:
"File":
handle_file_menu(button_item, id)
"Edit":
handle_edit_menu(button_item, id)
"Canvas":
handle_canvas_menu(button_item, id)
"Layer":
handle_layer_menu(button_item, id)
"Frame":
handle_frame_menu(button_item, id)
"Grid":
handle_grid_menu(button_item, id)
"Magic":
handle_magic_menu(button_item, id)
"Editor":
handle_editor_menu(button_item, id)
func handle_file_menu(pressed_item: String, id):
match pressed_item:
"Save Project":
owner.get_node("SaveFileDialog").open_save_project()
"Load Project":
owner.get_node("LoadFileDialog").open_load_project()
"New Project":
owner.get_node("ConfirmationDialog").show()
"Export (PNG)":
owner.get_node("SaveFileDialog").open_save_current_frame()
"Export Frame":
owner.get_node("SaveFileDialog").open_save_current_frame()
"Export Layer":
owner.get_node("SaveFileDialog").open_save_current_layer()
"Export Selection":
print("Not implemented!")
"Import (PNG)":
owner.get_node("LoadFileDialog").open_import_image()
func handle_edit_menu(pressed_item: String, id):
match pressed_item:
"Add Layer":
editor.add_new_layer()
func handle_canvas_menu(pressed_item: String, id):
match pressed_item:
"Change Size":
owner.get_node("ChangeCanvasSize").show()
"Crop To Content":
owner.paint_canvas.crop_to_content()
func handle_layer_menu(pressed_item: String, id):
match pressed_item:
"Add Layer":
editor.add_new_layer()
"Delete Layer":
editor.remove_active_layer()
"Duplicate Layer":
editor.duplicate_active_layer()
"Clear Layer":
owner.paint_canvas.clear_active_layer()
"Toggle Alpha Locked":
owner.paint_canvas.active_layer.toggle_alpha_locked()
$Buttons/Layer.get_popup().set_item_checked(id, not $Buttons/Layer.get_popup().is_item_checked(id))
owner.find_node("LockAlpha").pressed = $Buttons/Layer.get_popup().is_item_checked(id)
func handle_frame_menu(pressed_item: String, id):
match pressed_item:
"Add Frame":
var frame_button = editor.anim_panel.get_animation_stripe(editor.current_animation_idx).add_new_frame_button()
editor._on_add_frame_pressed(editor.current_animation_idx, frame_button.get_index())
"Delete Frame":
editor.delete_current_frame()
"Duplicate Frame":
pass
"Clear Frame Layers":
pass
_:
printerr("Pressed: ", pressed_item, " not found!")
func handle_grid_menu(pressed_item: String, id):
match pressed_item:
"Change Grid Size":
owner.get_node("ChangeGridSizeDialog").show()
"Toggle Grid":
owner.paint_canvas.toggle_grid()
func handle_magic_menu(pressed_item: String, id):
match pressed_item:
"Add Layer":
editor.add_new_layer()
func handle_editor_menu(pressed_item: String, id):
match pressed_item:
"Settings":
owner.get_node("Settings").show()
"Show Preview":
owner.preview_window.visible = not owner.preview_window.visible
$Buttons/Editor.get_popup().set_item_checked(id, owner.preview_window.visible)
"Toggle Grid":
var grids_node = owner.find_node("Grids")
grids_node.visible = !grids_node.visible
"Reset Canvas Position":
owner.paint_canvas_node.rect_position = Vector2(0, 0)
"Show Animation Panel":
owner.anim_panel.visible = not owner.anim_panel.visible
$Buttons/Editor.get_popup().set_item_checked(id, owner.anim_panel.visible)
func is_any_menu_open() -> bool:
for child in $Buttons.get_children():
if child.get_popup().visible:
return true
return false

View File

@ -0,0 +1,500 @@
tool
extends Control
var image = Image.new()
var last_pixel = []
onready var canvas_image_node = get_node("CanvasImage")
export var grid_size = 16
export var canvas_size = Vector2(48, 28)
export var region_size = 10
export var can_draw = true
var mouse_in_region
var mouse_on_top
#terms
#global cell - a cell that has a global grid position on the canvas
#local cell - a cell that has a local grid position in a chunk region on the canvas
#chunk region - a set of cells contained in an even number
#TODO: Maybe each chunk region can hold an image resource
# so that way the engine wouldn't lag at all when updating the canvas
var layers = {}
var active_layer
var preview_layer = "preview"
var preview_enabled = false
func _enter_tree():
#----------------------
# init Layer
#----------------------
layers[preview_layer] = {
"layer": null,
"data": [],
"chunks": null,
}
canvas_size = Vector2(int(rect_size.x / grid_size), int(rect_size.y / grid_size))
#print("canvas_size: ", canvas_size)
func _ready():
active_layer = add_existing_layer(get_tree().get_nodes_in_group("layer")[0])
#print("active Layer: ", active_layer)
func get_layer_data(layer_name):
return layers[layer_name]
func get_active_layer():
return layers[active_layer]
func get_preview_layer():
return layers[preview_layer]
func clear_active_layer():
for pixel in layers[active_layer].data:
set_global_cell_in_chunk(pixel[0], pixel[1], Color(0,0,0,0))
func clear_layer(layer_name: String):
for pixel in layers[layer_name].data:
set_global_cell_in_chunk(pixel[0], pixel[1], Color(0,0,0,0))
func clear_preview_layer():
for pixel in layers["preview"].data:
set_global_cell_in_chunk(pixel[0], pixel[1], Color(0,0,0,0))
func remove_layer(layer_name):
get_node("ChunkNodes").remove_child(layers[layer_name].chunks)
layers[layer_name].chunks.queue_free()
layers.erase(layer_name)
if active_layer == layer_name:
for layer in layers:
if layer == preview_layer:
continue
active_layer = layer
break
return active_layer
# only needed for init
func add_existing_layer(layer):
layers[layer.name] = {
"layer": layer,
"data": [],
"chunks": null,
}
generate_chunks()
return layer.name
func add_new_layer(layer_name):
layers[layer_name] = {
"layer": null,
"data": [],
"chunks": null,
}
generate_chunks()
return layer_name
func duplicate_layer(layer: String, neu_layer_name: String):
var _preview = preview_enabled
preview_enabled = false
var _temp = active_layer
active_layer = neu_layer_name
layers[neu_layer_name] = {
"layer": null,
"data": layers[layer].data.duplicate(true),
"chunks": null,
}
generate_chunks()
# get_node("ChunkNodes").remove_child(layers[neu_layer_name].chunks)
# get_node("ChunkNodes").add_child_below_node(layers[layer].chunks, layers[neu_layer_name].chunks, true)
for pixel in layers[neu_layer_name].data:
set_pixel_cell(pixel[0], pixel[1], pixel[2])
active_layer = _temp
preview_enabled = _preview
return neu_layer_name
func toggle_layer_visibility(layer_name):
layers[layer_name].chunks.visible = not layers[layer_name].chunks.visible
#print("Layer: ", layer_name, " is now: ", layers[layer_name].chunks.visible)
var util = preload("res://addons/Godoxel/Util.gd")
func _on_mouse_entered():
mouse_on_top = true
func _on_mouse_exited():
mouse_on_top = false
func _process(delta: float) -> void:
var mouse_position = get_local_mouse_position()
var rect = Rect2(Vector2(0, 0), rect_size)
mouse_in_region = rect.has_point(mouse_position)
update()
#if not Engine.editor_hint:
# print(mouse_on_canvas, " | ", has_focus())
#draw_canvas_out just updates the image constantly
#if can_draw:
# draw_canvas_out()
func generate_chunks():
var maxium_chunk_size = get_maxium_filled_chunks()
#TODO: We probably don't need to check for x and y anymore
for key in layers:
if layers[key].chunks != null:
continue
var chunk_node = Control.new()
get_node("ChunkNodes").add_child(chunk_node)
chunk_node.owner = self
layers[key].chunks = chunk_node
for x in maxium_chunk_size.x:
for y in maxium_chunk_size.y:
var paint_canvas_chunk = load("res://addons/Godoxel/PaintCanvasChunk.tscn").instance()
paint_canvas_chunk.setup(region_size)
paint_canvas_chunk.name = "C-%s-%s" % [x, y]
paint_canvas_chunk.rect_position = \
Vector2(x * (grid_size * region_size), y * (grid_size * region_size))
layers[key].chunks.add_child(paint_canvas_chunk)
func get_maxium_filled_chunks():
return Vector2(canvas_size.x / region_size, canvas_size.y / region_size).ceil()
##TODO: Remake these functions with godot's setget features
#func resize_grid(grid):
# #print(grid)
# if grid <= 0:
# return
# grid_size = grid
# canvas_image_node.rect_scale = Vector2(grid, grid)
#
#
#func resize_canvas(x, y):
# image.unlock()
# image.create(x, y, true, Image.FORMAT_RGBA8)
# canvas_size = Vector2(x, y)
# #setup_all_chunks()
# image.lock()
#func draw_canvas_out(a = ""):
# if canvas_image_node == null:
# return
# var image_texture = ImageTexture.new()
# image_texture.create_from_image(image)
# image_texture.set_flags(0)
# canvas_image_node.texture = image_texture
func get_wrapped_region_cell(x, y):
return Vector2(wrapi(x, 0, region_size), wrapi(y, 0, region_size))
func get_region_from_cell(x, y):
return Vector2(floor(x / region_size), floor(y / region_size))
func set_local_cell_in_chunk(chunk_x, chunk_y, local_cell_x, local_cell_y, color):
var chunk_node
if preview_enabled:
chunk_node = layers.preview.chunks.get_node_or_null("C-%s-%s" % [chunk_x, chunk_y])
else:
chunk_node = layers[active_layer].chunks.get_node_or_null("C-%s-%s" % [chunk_x, chunk_y])
if chunk_node == null:
#print("Can't find chunk node!")
return
chunk_node.set_cell(local_cell_x, local_cell_y, color)
func set_global_cell_in_chunk(cell_x, cell_y, color):
var chunk = get_region_from_cell(cell_x, cell_y)
var wrapped_cell = get_wrapped_region_cell(cell_x, cell_y)
set_local_cell_in_chunk(chunk.x, chunk.y, wrapped_cell.x, wrapped_cell.y, color)
#func update_chunk_region_from_cell(x, y):
# var region_to_update = get_region_from_cell(x, y)
# update_chunk_region(region_to_update.x, region_to_update.y)
func get_pixel_cell_color(x, y):
if not cell_in_canvas_region(x, y):
return null
var pixel_cell = get_pixel_cell(x, y)
if pixel_cell == null:
#We already checked that the cell can't be out of the canvas
#region so we can assume the pixel cell is completely transparent if it's null
return Color(0, 0, 0, 0)
else:
return util.color_from_array(pixel_cell[2])
func get_pixel_cell_color_v(vec2):
return get_pixel_cell_color(vec2.x, vec2.y)
func get_pixel_cell(x, y):
if active_layer == null:
return
if not cell_in_canvas_region(x, y):
return null
for pixel in get_active_layer().data:
if pixel[0] == x and pixel[1] == y:
return pixel
return null
func get_pixel_cell_v(vec2):
return get_pixel_cell(vec2.x, vec2.y)
#func remove_pixel_cell(x, y):
# if can_draw == false:
# return false
# if not cell_in_canvas_region(x, y):
# return false
# var layer_data = get_layer_data("Layer 1")
# for pixel in range(0, layer_data.size()):
# if layer_data[pixel][0] == x and layer_data[pixel][1] == y:
# layer_data.remove(pixel)
# #update_chunk_region_from_cell(x, y)
# #TOOD: If pixel exists in temp_pool_pixels then remove it
# image.set_pixel(x, y, Color(0, 0, 0, 0))
# return true
# return false
#func remove_pixel_cell_v(vec2):
# return remove_pixel_cell(vec2.x, vec2.y)
func set_pixel_cell(x, y, color):
if can_draw == false:
return false
if not cell_in_canvas_region(x, y):
return false
var layer
if preview_enabled:
layer = get_preview_layer()
else:
layer = get_active_layer()
var index = 0
for pixel in layer.data:
#TODO: Make a better way of accessing the array because the more pixels we have,
#the longer it takes to
#set the pixel
if pixel[0] == x and pixel[1] == y:
#No reason to set the pixel again if the colors are the same
#If the color we are setting is 0, 0, 0, 0 then there is
#no reason to keep the information about the pixel
#so we remove it from the layer data
if color == Color(0, 0, 0, 0):
layer.data.remove(index)
else:
pixel[2] = color
#TODO: The new system is going to allow chunks to each have their own TextureRect and Image
#nodes so what we are doing in here is
#that we are setting the local cell in the region of that image
set_global_cell_in_chunk(x, y, color)
last_pixel = [x, y, color]
return true
index += 1
#don't append any data if the color is 0, 0, 0, 0
if color != Color(0, 0, 0, 0):
#if the pixel data doesn't exist then we add it in
layer.data.append([x, y, color])
set_global_cell_in_chunk(x, y, color)
last_pixel = [x, y, color]
return true
func set_pixel_cell_v(vec2, color):
return set_pixel_cell(vec2.x, vec2.y, color)
func set_pixels_from_line(vec2_1, vec2_2, color):
var points = get_pixels_from_line(vec2_1, vec2_2)
for i in points:
set_pixel_cell_v(i, color)
func set_random_pixels_from_line(vec2_1, vec2_2):
var points = get_pixels_from_line(vec2_1, vec2_2)
for i in points:
set_pixel_cell_v(i, util.random_color_alt())
func get_pixels_from_line(vec2_1, vec2_2):
var points = PoolVector2Array()
var dx = abs(vec2_2.x - vec2_1.x)
var dy = abs(vec2_2.y - vec2_1.y)
var x = vec2_1.x
var y = vec2_1.y
var sx = 0
if vec2_1.x > vec2_2.x:
sx = -1
else:
sx = 1
var sy = 0
if vec2_1.y > vec2_2.y:
sy = -1
else:
sy = 1
if dx > dy:
var err = dx / 2
while(true):
if x == vec2_2.x:
break
points.push_back(Vector2(x, y))
err -= dy
if err < 0:
y += sy
err += dx
x += sx
else:
var err = dy / 2
while (true):
if y == vec2_2.y:
break
points.push_back(Vector2(x, y))
err -= dx
if err < 0:
x += sx
err += dy
y += sy
points.push_back(Vector2(x, y))
return points
#even though the function checks for it, we can't afford adding more functions to the call stack
#because godot has a limit until it crashes
var flood_fill_queue = 0
func flood_fill(x, y, target_color, replacement_color):
#yield(get_tree().create_timer(1), "timeout")
flood_fill_queue += 1
if not cell_in_canvas_region(x, y):
flood_fill_queue -= 1
return
if target_color == replacement_color:
flood_fill_queue -= 1
return
elif not get_pixel_cell_color(x, y) == target_color:
flood_fill_queue -= 1
return
else:
set_pixel_cell(x, y, replacement_color)
if flood_fill_queue >= 500:
#print(flood_fill_queue)
yield(get_tree().create_timer(0.01), "timeout")
#up
if get_pixel_cell_color(x, y - 1) == target_color:
flood_fill(x, y - 1, target_color, replacement_color)
#down
if get_pixel_cell_color(x, y + 1) == target_color:
flood_fill(x, y + 1, target_color, replacement_color)
#left
if get_pixel_cell_color(x - 1, y) == target_color:
flood_fill(x - 1, y, target_color, replacement_color)
#right
if get_pixel_cell_color(x + 1, y) == target_color:
flood_fill(x + 1, y, target_color, replacement_color)
flood_fill_queue -= 1
return
#func flood_fill_erase(x, y, target_color):
# yield(get_tree().create_timer(0.001), "timeout")
# if not cell_in_canvas_region(x, y):
# print("cell not in canvas")
# return
# #if target_color == replacement_color:
# # return
# elif not get_pixel_cell_color(x, y) == target_color:
# print("cell doesn't match pixel color")
# return
# elif not get_pixel_cell(x, y):
# print("cell already erased")
# return
# else:
# print("removed pixel")
# remove_pixel_cell(x, y)
# print("x: ", x, " y: ", y, " color: ", target_color)
# #up
# flood_fill_erase(x, y - 1, target_color)
# #down
# flood_fill_erase(x, y + 1, target_color)
# #left
# flood_fill_erase(x - 1, y, target_color)
# #right
# flood_fill_erase(x + 1, y, target_color)
# return
func cell_in_canvas_region(x, y):
if x > canvas_size.x - 1 or x < 0 or y > canvas_size.y - 1 or y < 0:
#out of bounds, return false
return false
else:
return true
#Both of these functions right now just return the starting
#position of the canvas and the last position of the canvas
func get_all_used_regions_in_canvas():
var first_used_region = get_first_used_region_in_canvas()
var last_used_region = get_last_used_region_in_canvas()
var chunk_pool = PoolVector2Array()
for chunk_x in range(first_used_region.x, last_used_region.x):
for chunk_y in range(first_used_region.y, last_used_region.y):
chunk_pool.append(Vector2(chunk_x, chunk_y))
return chunk_pool
func get_first_used_region_in_canvas():
return get_region_from_cell(0, 0)
func get_last_used_region_in_canvas():
return get_region_from_cell(canvas_size.x - 1, canvas_size.y - 1)
func get_cells_in_region(x, y):
var start_cell = Vector2(x * region_size, y * region_size)
var end_cell = Vector2((x * region_size) + region_size, (y * region_size) + region_size)
var cell_array = []
for cx in range(start_cell.x, end_cell.x):
for cy in range(start_cell.y, end_cell.y):
var pixel_cell = get_pixel_cell(cx, cy)
if pixel_cell == null:
pixel_cell = [cx, cy, Color(0, 0, 0, 0)]
cell_array.append(pixel_cell)
return cell_array

View File

@ -0,0 +1,29 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/Godoxel/PaintCanvas.gd" type="Script" id=1]
[node name="PaintCanvas" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
canvas_size = Vector2( 0, 0 )
[node name="ChunkNodes" type="Control" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CanvasImage" type="TextureRect" parent="."]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
expand = true
stretch_mode = 3

View File

@ -0,0 +1,27 @@
tool
extends Control
var image = Image.new()
var image_texture = ImageTexture.new()
func _ready():
mouse_filter = Control.MOUSE_FILTER_IGNORE
func setup(region_size):
image.create(region_size, region_size, true, Image.FORMAT_RGBA8)
image.lock()
func update_chunk():
image_texture.create_from_image(image)
image_texture.set_flags(0)
self.texture = image_texture
func set_cell(x, y, color):
image.set_pixel(x, y, color)
update_chunk()
func _on_VisibilityNotifier2D_screen_entered():
visible = true
func _on_VisibilityNotifier2D_screen_exited():
visible = false

View File

@ -0,0 +1,13 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/Godoxel/PaintCanvasChunk.gd" type="Script" id=1]
[node name="PaintCanvasChunk" type="TextureRect"]
margin_right = 10.0
margin_bottom = 10.0
rect_scale = Vector2( 16, 16 )
mouse_filter = 2
expand = true
stretch_mode = 1
script = ExtResource( 1 )

View File

@ -0,0 +1,2 @@
tool
extends Control

View File

@ -0,0 +1,14 @@
extends GEDraggableWindow
tool
onready var preview_layers = $PreviewLayerTextures
func _ready() -> void:
show()
func update_preview(frame: GEFrame):
for idx in range(preview_layers.get_child_count()):
preview_layers.get_child(idx).texture = frame.layers[idx].texture

View File

@ -0,0 +1,129 @@
tool
extends FileDialog
enum SaveMode {
CURRENT_FRAME,
PROJECT,
CURRENT_LAYER,
}
var save_mode = SaveMode.CURRENT_FRAME
onready var canvas = get_parent().find_node("Canvas")
var file_path = ""
func _ready():
# warning-ignore:return_value_discarded
get_line_edit().connect("text_entered", self, "_on_LineEdit_text_entered")
invalidate()
clear_filters()
add_filter("*.png ; PNG Images")
#######################################################
# dialogs
#######################################################
func open_save_current_frame():
show()
invalidate()
clear_filters()
add_filter("*.png ; PNG Images")
save_mode = SaveMode.CURRENT_FRAME
func open_save_project():
show()
invalidate()
clear_filters()
add_filter("*.godoxel ; Godot - Godoxel")
save_mode = SaveMode.PROJECT
func open_save_current_layer():
show()
invalidate()
clear_filters()
add_filter("*.png ; PNG Images")
save_mode = SaveMode.CURRENT_LAYER
#######################################################
# dialogs
#######################################################
func _on_SaveFileDialog_file_selected(path: String):
file_path = path
match save_mode:
SaveMode.CURRENT_FRAME:
save_current_frame()
SaveMode.CURRENT_LAYER:
save_current_layer()
SaveMode.PROJECT:
save_project()
func save_current_layer():
var image = canvas.get_current_layer_image()
# overwrite image if exists
var dir = Directory.new()
if dir.file_exists(file_path):
dir.remove(file_path)
image.save_png(file_path)
# update file doc if using inside the editor
if Engine.is_editor_hint():
EditorPlugin.new().get_editor_interface().get_resource_filesystem().scan()
func save_project():
var save_data = owner.get_save_project_data()
# overwrite image if exists
var dir = Directory.new()
if dir.file_exists(file_path):
dir.remove(file_path)
var file = File.new()
file.open(file_path, File.WRITE)
file.store_string(JSON.print(save_data))
file.close()
# update file doc if using inside the editor
if Engine.is_editor_hint():
EditorPlugin.new().get_editor_interface().get_resource_filesystem().scan()
func save_current_frame():
var image = canvas.get_current_frame_image()
# overwrite image if exists
var dir = Directory.new()
if dir.file_exists(file_path):
dir.remove(file_path)
image.save_png(file_path)
# update file doc if using inside the editor
if Engine.is_editor_hint():
EditorPlugin.new().get_editor_interface().get_resource_filesystem().scan()
func _on_SaveFileDialog_about_to_show():
invalidate()
func _on_SaveFileDialog_visibility_changed():
invalidate()
#func _on_LineEdit_text_entered(text):
# return
func _on_SaveFileDialog_confirmed():
return

View File

@ -0,0 +1,24 @@
tool
extends Control
export var outline_size = 3
func _ready():
pass
func _process(delta):
update()
func _draw():
if not rect_size == Vector2():
draw_outline_box(rect_size, Color.gray, outline_size)
func draw_outline_box(size, color, width):
#Top line
draw_line(Vector2(0 + 1, 0), Vector2(size.x, 0), color, width)
#Left line
draw_line(Vector2(0 + 1, 0), Vector2(0, size.y), color, width)
#Bottom line
draw_line(Vector2(0 + 1, size.y), Vector2(size.x, size.y), color, width)
#Right line
draw_line(Vector2(size.x, 0), Vector2(size.x, size.y), color, width)

View File

@ -0,0 +1,24 @@
tool
extends Control
var editor
var canvas_outline
var start_time
var end_time
func _enter_tree():
canvas_outline = get_parent().find_node("CanvasOutline")
editor = get_parent()
func _on_ColorPickerButton_color_changed(color):
canvas_outline.color = color
func _on_CheckButton_toggled(button_pressed):
canvas_outline.visible = button_pressed
func _on_Ok_pressed():
hide()

View File

@ -0,0 +1,64 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/Godoxel/Settings.gd" type="Script" id=1]
[node name="Settings" type="WindowDialog"]
visible = true
margin_top = 20.0
margin_right = 300.0
margin_bottom = 120.0
window_title = "Settings"
script = ExtResource( 1 )
[node name="Ok" type="Button" parent="."]
margin_left = 210.0
margin_top = 70.0
margin_right = 290.0
margin_bottom = 90.0
text = "Ok"
[node name="CanvasOutlineToggle" type="Control" parent="."]
margin_left = 10.0
margin_top = 10.0
margin_right = 290.0
margin_bottom = 30.0
__meta__ = {
"_edit_group_": true
}
[node name="Label" type="Label" parent="CanvasOutlineToggle"]
margin_right = 130.0
margin_bottom = 20.0
text = "Canvas Outline:"
valign = 1
[node name="CheckButton" type="CheckButton" parent="CanvasOutlineToggle"]
margin_left = 210.0
margin_top = -10.0
margin_right = 286.0
margin_bottom = 30.0
pressed = true
[node name="CanvasOutlineColor" type="Control" parent="."]
margin_left = 10.0
margin_top = 40.0
margin_right = 290.0
margin_bottom = 60.0
__meta__ = {
"_edit_group_": true
}
[node name="Label" type="Label" parent="CanvasOutlineColor"]
margin_right = 130.0
margin_bottom = 20.0
text = "Canvas Outline Color:"
valign = 1
[node name="ColorPickerButton" type="ColorPickerButton" parent="CanvasOutlineColor"]
margin_left = 170.0
margin_right = 280.0
margin_bottom = 20.0
[connection signal="pressed" from="Ok" to="." method="_on_Ok_pressed"]
[connection signal="toggled" from="CanvasOutlineToggle/CheckButton" to="." method="_on_CheckButton_toggled"]
[connection signal="color_changed" from="CanvasOutlineColor/ColorPickerButton" to="." method="_on_ColorPickerButton_color_changed"]

View File

@ -0,0 +1,39 @@
tool
extends Control
var size = 240
#TODO: To make reading the text easier, the text info with the longest text should have it's length applied to all the
#the other text infos
func add_text_info(text_name, custom_node = null):
var last_text_info_child = null
var child_count = get_child_count()
if not child_count <= 0:
last_text_info_child = get_children()[get_children().size() - 1]
var label = Label.new()
label.name = text_name
label.rect_size = Vector2(size, 14)
if not last_text_info_child == null:
var x = last_text_info_child.rect_position.x
var y = last_text_info_child.rect_position.y
var temp_size = size
if child_count == 4:
x = 0
y = 20
temp_size = 0
label.rect_position = Vector2(x + temp_size, y)
if not custom_node == null:
label.add_child(custom_node)
add_child(label)
func update_text_info(text_name, text_value = null, node = null, node_target_value = null, node_value = null):
var text_label = self.get_node(text_name)
if text_label == null:
return
if not node == null:
get_node(text_name).get_node(node).set(node_target_value, node_value)
if text_value == null:
text_label.text = "%s: %s" % [text_name, null]
else:
text_label.text = "%s: %s" % [text_name, String(text_value)]

94
addons/Godoxel/Util.gd Normal file
View File

@ -0,0 +1,94 @@
tool
extends Node
class_name GEUtils
static func get_pixels_in_line(from: Vector2, to: Vector2):
var dx = to[0] - from[0]
var dy = to[1] - from[1]
var nx = abs(dx)
var ny = abs(dy)
var signX = sign(dx)
var signY = sign(dy)
var p = from
var points : Array = [p]
var ix = 0
var iy = 0
while ix < nx || iy < ny:
if (1 + (ix << 1)) * ny < (1 + (iy << 1)) * nx:
p[0] += signX
ix +=1
else:
p[1] += signY
iy += 1
points.append(p)
return points
static func to_1D_v(p, w) -> int:
return p.x + p.y * w
static func to_1D(x, y, w) -> int:
return x + y * w
static func to_2D(idx, w) -> Vector2:
var p = Vector2()
p.x = int(idx) % int(w)
p.y = int(idx / w)
return p
static func color_from_array(color_array):
var r = color_array[0]
var g = color_array[1]
var b = color_array[2]
var a = color_array[3]
return Color(r, g, b, a)
static func random_color():
return Color(randf(), randf(), randf())
static func random_color_alt():
var rand = randi() % 6
match rand:
#red
0:
return Color.red
#blue
1:
return Color.blue
#green
2:
return Color.green
#orange
3:
return Color.orange
#yellow
4:
return Color.yellow
#purple
5:
return Color.purple
static func get_line_string(file, number):
return file.get_as_text().split("\n")[number - 1].strip_edges()
static func printv(variable):
var stack = get_stack()[get_stack().size() - 1]
var line = stack.line
var source = stack.source
var file = File.new()
file.open(source, File.READ)
var line_string = get_line_string(file, line)
file.close()
var left_p = line_string.find("(")
var left_p_string = line_string.right(left_p + 1)
var right_p = left_p_string.find(")")
var variable_name = left_p_string.left(right_p)
print("%s: %s" % [variable_name, variable])

View File

@ -0,0 +1,6 @@
extends ViewportContainer
tool
func _ready():
get_child(0).size = rect_size

View File

@ -0,0 +1,41 @@
tool
extends Control
export var color = Color()
export var size:int = 16
export var zoom = 0
export var offset = Vector2(0, 0)
func _enter_tree():
set_process(true)
func _draw():
if size == 0:
size = 1
var temp_size = size + zoom
var wrap_offset = Vector2(wrapf(offset.x, 0, temp_size), wrapf(offset.y, 0, temp_size))
var ceil_x = ceil(rect_size.x / temp_size)
var ceil_y = ceil(rect_size.y / temp_size)
for i in ceil_y:
var start_x = Vector2(0, (i * temp_size) + wrap_offset.y)
var end_x = Vector2(rect_size.x, (i * temp_size) + wrap_offset.y)
# var end_x = Vector2(int(rect_size.x) + size - int(rect_size.x) % size, (i * temp_size) + wrap_offset.y)
draw_line(start_x, end_x, color, 1)
for i in ceil_x:
var start_y = Vector2((i * temp_size) + wrap_offset.x, 0)
var end_y = Vector2((i * temp_size) + (wrap_offset.x), rect_size.y)
# var end_y = Vector2((i * temp_size) + (wrap_offset.x), int(rect_size.y) + size - int(rect_size.y) % size)
draw_line(start_y, end_y, color, 1)
func _process(delta):
if not is_visible_in_tree():
return
update()

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/Godoxel/VisualGrid.gd" type="Script" id=1]
[node name="VisualGrid" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )

View File

@ -0,0 +1,123 @@
extends Node
class_name GEAction
var action_data = {}
func _init():
action_data["redo"] = {}
action_data["undo"] = {}
action_data["preview"] = {}
func do_action(canvas, data: Array):
if not "cells" in action_data.redo:
action_data.redo["cells"] = []
action_data.redo["colors"] = []
if not "cells" in action_data.undo:
action_data.undo["cells"] = []
action_data.undo["colors"] = []
if not "cells" in action_data.preview:
action_data.preview["cells"] = []
action_data.preview["colors"] = []
if not "layer" in action_data:
action_data["layer"] = canvas.active_layer
func commit_action(canvas):
print("NO IMPL commit_action ")
return []
func undo_action(canvas):
print("NO IMPL undo_action ")
func redo_action(canvas):
print("NO IMPL redo_action ")
func can_commit() -> bool:
return not action_data.redo.empty()
func get_x_sym_points(canvas_width, pixel):
var p = int(canvas_width - pixel.x)
var all_points = [pixel, Vector2(p-1, pixel.y)]
var points :Array = []
for point in all_points:
if point in points:
continue
points.append(point)
return points
func get_y_sym_points(canvas_height, pixel):
var p = int(canvas_height - pixel.y)
var all_points = [pixel, Vector2(pixel.x, p-1)]
var points :Array = []
for point in all_points:
if point in points:
continue
points.append(point)
return points
func get_xy_sym_points(canvas_width, canvas_height, pixel):
var all_points = []
var xpoints = get_x_sym_points(canvas_width, pixel)
all_points += get_y_sym_points(canvas_height, xpoints[0])
all_points += get_y_sym_points(canvas_height, xpoints[1])
var points :Array = []
for point in all_points:
if point in points:
continue
points.append(point)
return points
func get_points(canvas, pixel):
var points = []
if canvas.symmetry_x and canvas.symmetry_y:
var sym_points = get_xy_sym_points(canvas.canvas_width, canvas.canvas_height, pixel)
for point in sym_points:
if point in action_data.undo.cells or canvas.get_pixel_v(point) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
points.append(point)
elif canvas.symmetry_y:
var sym_points = get_y_sym_points(canvas.canvas_height, pixel)
for point in sym_points:
if point in action_data.undo.cells or canvas.get_pixel_v(point) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
points.append(point)
elif canvas.symmetry_x:
var sym_points = get_x_sym_points(canvas.canvas_width, pixel)
for point in sym_points:
if point in action_data.undo.cells or canvas.get_pixel_v(point) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
points.append(point)
else:
if pixel in action_data.undo.cells or canvas.get_pixel_v(pixel) == null:
return []
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
return []
points.append(pixel)
return points

View File

@ -0,0 +1,53 @@
extends GEAction
class_name GEBrighten
const brighten_color = 0.1
func do_action(canvas, data: Array):
.do_action(canvas, data)
var pixels = GEUtils.get_pixels_in_line(data[0], data[1])
for pixel in pixels:
if canvas.get_pixel_v(pixel) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
if pixel in action_data.undo.cells:
var brightened_color = canvas.get_pixel_v(pixel).lightened(0.1)
canvas.set_pixel_v(pixel, brightened_color)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(brightened_color)
continue
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
var brightened_color = canvas.get_pixel_v(pixel).lightened(0.1)
canvas.set_pixel_v(pixel, brightened_color)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(brightened_color)
func commit_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,47 @@
extends GEAction
class_name GEBrush
func do_action(canvas: GECanvas, data: Array):
.do_action(canvas, data)
for pixel in GEUtils.get_pixels_in_line(data[0], data[1]):
for off in BrushPrefabs.get_brush(data[3], data[4]):
var p = pixel + off
if p in action_data.undo.cells or canvas.get_pixel_v(p) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(p) == Color.transparent:
continue
action_data.undo.colors.append(canvas.get_pixel_v(p))
action_data.undo.cells.append(p)
canvas.set_pixel_v(p, data[2])
action_data.redo.cells.append(p)
action_data.redo.colors.append(data[2])
func commit_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,49 @@
extends GEAction
class_name GEBucket
func do_action(canvas, data: Array):
.do_action(canvas, data)
if canvas.get_pixel_v(data[0]) == data[2]:
return
var pixels = canvas.select_same_color(data[0].x, data[0].y)
for pixel in pixels:
if pixel in action_data.undo.cells:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
canvas.set_pixel_v(pixel, data[2])
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(data[2])
func commit_action(canvas):
var cells = action_data.preview.cells
var colors = action_data.preview.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,82 @@
extends GEAction
class_name GECut
const selection_color = Color(0.8, 0.8, 0.8, 0.5)
var mouse_start_pos = null
var mouse_end_pos = null
func can_commit() -> bool:
return false #ugly way of handling a cut
func do_action(canvas, data: Array):
.do_action(canvas, data)
if mouse_start_pos == null:
mouse_start_pos = data[0]
mouse_end_pos = data[0]
action_data.preview.cells.clear()
action_data.preview.colors.clear()
canvas.clear_preview_layer()
var p = mouse_start_pos
var s = mouse_end_pos - mouse_start_pos
var pixels = GEUtils.get_pixels_in_line(p, p + Vector2(s.x, 0))
pixels += GEUtils.get_pixels_in_line(p, p + Vector2(0, s.y))
pixels += GEUtils.get_pixels_in_line(p + s, p + s + Vector2(0, -s.y))
pixels += GEUtils.get_pixels_in_line(p + s, p + s + Vector2(-s.x, 0))
for pixel in pixels:
canvas.set_preview_pixel_v(pixel, selection_color)
action_data.preview.cells.append(pixel)
action_data.preview.colors.append(selection_color)
func commit_action(canvas):
canvas.clear_preview_layer()
var p = mouse_start_pos
var s = mouse_end_pos - mouse_start_pos
for x in range(abs(s.x)+1):
for y in range(abs(s.y)+1):
var px = x
var py = y
if s.x < 0:
px *= -1
if s.y < 0:
py *= -1
var pos = p + Vector2(px, py)
var color = canvas.get_pixel(pos.x, pos.y)
if color == null or color.a == 0.0:
continue
action_data.redo.cells.append(pos)
action_data.redo.colors.append(canvas.get_pixel_v(pos))
canvas.set_pixel_v(pos, Color.transparent)
action_data.undo.cells.append(pos)
action_data.undo.colors.append(Color.transparent)
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,55 @@
extends GEAction
class_name GEDarken
const dark_factor = 0.1
func do_action(canvas, data: Array):
.do_action(canvas, data)
var pixels = GEUtils.get_pixels_in_line(data[0], data[1])
for pixel in pixels:
if canvas.get_pixel_v(pixel) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
if pixel in action_data.undo.cells:
var darkened_color = canvas.get_pixel_v(pixel).darkened(dark_factor)
canvas.set_pixel_v(pixel, darkened_color)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(darkened_color)
continue
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
var darkened_color = canvas.get_pixel_v(pixel).darkened(dark_factor)
canvas.set_pixel_v(pixel, darkened_color)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(darkened_color)
func commit_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,59 @@
extends GEAction
class_name GELine
var mouse_start_pos = null
func do_action(canvas, data: Array):
.do_action(canvas, data)
if mouse_start_pos == null:
mouse_start_pos = data[0]
action_data.preview.cells.clear()
action_data.preview.colors.clear()
canvas.clear_preview_layer()
var pixels = GEUtils.get_pixels_in_line(data[0], mouse_start_pos)
for pixel in pixels:
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
canvas.set_preview_pixel_v(pixel, data[2])
action_data.preview.cells.append(pixel)
action_data.preview.colors.append(data[2])
func commit_action(canvas):
canvas.clear_preview_layer()
var cells = action_data.preview.cells
var colors = action_data.preview.colors
for idx in range(cells.size()):
if canvas.get_pixel_v(cells[idx]) == null:
continue
action_data.undo.cells.append(cells[idx])
action_data.undo.colors.append(canvas.get_pixel_v(cells[idx]))
canvas.set_pixel_v(cells[idx], colors[idx])
action_data.redo.cells.append(cells[idx])
action_data.redo.colors.append(colors[idx])
mouse_start_pos = null
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,44 @@
extends GEAction
class_name GEMultiLine
func can_commit() -> bool:
return false
func update_action(canvas, data: Array):
.update_action(canvas, data)
var pixels = GEUtils.get_pixels_in_line(data[0], data[1])
for pixel in pixels:
if pixel in action_data.undo.cells or canvas.get_pixel_v(pixel) == null or canvas.is_alpha_locked():
continue
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
canvas.set_pixel_v(pixel, data[2])
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(data[2])
func commit_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,59 @@
extends GEAction
class_name GEPasteCut
#data[2] = selection_pos
#data[3] = selection_color
#data[4] = cut pos
#data[5] = cut size
func do_action(canvas, data: Array):
.do_action(canvas, data)
for pixel_pos in GEUtils.get_pixels_in_line(data[0], data[1]):
for idx in range(data[2].size()):
var pixel = data[2][idx]
var color = data[3][idx]
pixel -= data[4] + data[5] / 2
pixel += pixel_pos
if canvas.get_pixel_v(pixel) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
var found = action_data.redo.cells.find(pixel)
if found == -1:
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(color)
else:
action_data.redo.colors[found] = color
found = action_data.undo.cells.find(pixel)
if found == -1:
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
canvas.set_pixel_v(pixel, color)
func commit_action(canvas):
canvas.clear_preview_layer()
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,43 @@
extends GEAction
class_name GEPencil
func do_action(canvas, data: Array):
.do_action(canvas, data)
var pixels = GEUtils.get_pixels_in_line(data[0], data[1])
for pixel in pixels:
for p in get_points(canvas, pixel):
_set_pixel(canvas, p, data[2])
func _set_pixel(canvas, pixel, color):
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
canvas.set_pixel_v(pixel, color)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(color)
func commit_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,58 @@
extends GEAction
class_name GERainbow
func do_action(canvas, data: Array):
.do_action(canvas, data)
var pixels = GEUtils.get_pixels_in_line(data[0], data[1])
for pixel in pixels:
if canvas.get_pixel_v(pixel) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
if pixel in action_data.undo.cells:
var color = GEUtils.random_color()
canvas.set_pixel_v(pixel, color)
var idx = action_data.redo.cells.find(pixel)
action_data.redo.cells.remove(idx)
action_data.redo.colors.remove(idx)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(color)
continue
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.undo.cells.append(pixel)
var color = GEUtils.random_color()
canvas.set_pixel_v(pixel, color)
action_data.redo.cells.append(pixel)
action_data.redo.colors.append(color)
func commit_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

View File

@ -0,0 +1,69 @@
extends GEAction
class_name GERect
var mouse_start_pos = null
func do_action(canvas, data: Array):
.do_action(canvas, data)
if mouse_start_pos == null:
mouse_start_pos = data[0]
#print("init:", mouse_start_pos)
action_data.undo.cells.clear()
action_data.undo.colors.clear()
action_data.preview.cells.clear()
action_data.preview.colors.clear()
canvas.clear_preview_layer()
var p = mouse_start_pos
var s = data[0] - mouse_start_pos
var pixels = GEUtils.get_pixels_in_line(p, p + Vector2(s.x, 0))
pixels += GEUtils.get_pixels_in_line(p, p + Vector2(0, s.y))
pixels += GEUtils.get_pixels_in_line(p + s, p + s + Vector2(0, -s.y))
pixels += GEUtils.get_pixels_in_line(p + s, p + s + Vector2(-s.x, 0))
for pixel in pixels:
if canvas.get_pixel_v(pixel) == null:
continue
if canvas.is_alpha_locked() and canvas.get_pixel_v(pixel) == Color.transparent:
continue
canvas.set_preview_pixel_v(pixel, data[2])
action_data.undo.cells.append(pixel)
action_data.undo.colors.append(canvas.get_pixel_v(pixel))
action_data.preview.cells.append(pixel)
action_data.preview.colors.append(data[2])
func commit_action(canvas):
canvas.clear_preview_layer()
var cells = action_data.preview.cells
var colors = action_data.preview.colors
for idx in range(cells.size()):
canvas.set_pixel_v(cells[idx], colors[idx])
action_data.redo.cells.append(cells[idx])
action_data.redo.colors.append(colors[idx])
mouse_start_pos = null
return []
func undo_action(canvas):
var cells = action_data.undo.cells
var colors = action_data.undo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])
func redo_action(canvas):
var cells = action_data.redo.cells
var colors = action_data.redo.colors
for idx in range(cells.size()):
canvas._set_pixel_v(action_data.layer, cells[idx], colors[idx])

BIN
addons/Godoxel/assets/BrushCircle.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/BrushCircle.png-dd250909fee7964ffc38f7e4fcfe9c07.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Godoxel/assets/BrushCircle.png"
dest_files=[ "res://.import/BrushCircle.png-dd250909fee7964ffc38f7e4fcfe9c07.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

BIN
addons/Godoxel/assets/BrushCircle_Hovered.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/BrushCircle_Hovered.png-ae1a4d835af51e8a293b71d6a241b71c.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Godoxel/assets/BrushCircle_Hovered.png"
dest_files=[ "res://.import/BrushCircle_Hovered.png-ae1a4d835af51e8a293b71d6a241b71c.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

BIN
addons/Godoxel/assets/BrushHLine.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/BrushHLine.png-9182ec8ac804af16d356bf911782e299.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Godoxel/assets/BrushHLine.png"
dest_files=[ "res://.import/BrushHLine.png-9182ec8ac804af16d356bf911782e299.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

BIN
addons/Godoxel/assets/BrushHLine_Hovered.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/BrushHLine_Hovered.png-e51d5f3c1628c510a225057f3ed60d5a.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Godoxel/assets/BrushHLine_Hovered.png"
dest_files=[ "res://.import/BrushHLine_Hovered.png-e51d5f3c1628c510a225057f3ed60d5a.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=1.0

BIN
addons/Godoxel/assets/BrushRect.png (Stored with Git LFS) Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More