Merged "Scripts" and "Prefabs" folders into "src"

Made a new "src" folder that will contain the source code files, like all the GDScript and scene files. Please read this for more details: https://www.gdquest.com/docs/guidelines/best-practices/godot-gdscript/

It made no sense to keep scenes separate from their scripts. More file organizing will follow soon.
This commit is contained in:
OverloadedOrama 2020-05-01 22:17:05 +03:00
parent 5a54235604
commit 646fc19a70
63 changed files with 135 additions and 113 deletions

24
src/AnimationTag.tscn Normal file
View file

@ -0,0 +1,24 @@
[gd_scene format=2]
[node name="AnimationTag" type="VBoxContainer"]
margin_right = 39.0
margin_bottom = 32.0
rect_min_size = Vector2( 39, 32 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Line2D" type="Line2D" parent="."]
points = PoolVector2Array( 0, 32, 0, 0, 39, 0, 39, 32 )
width = 1.0
texture_mode = 1313163520
[node name="Label" type="Label" parent="."]
margin_left = 7.0
margin_right = 32.0
margin_bottom = 32.0
size_flags_horizontal = 4
size_flags_vertical = 3
text = "Idle"
align = 1
valign = 1

494
src/AnimationTimeline.gd Normal file
View file

@ -0,0 +1,494 @@
extends Panel
var fps := 6.0
var animation_loop := 1 # 0 is no loop, 1 is cycle loop, 2 is ping-pong loop
var animation_forward := true
var first_frame := 0
var last_frame := Global.canvases.size() - 1
onready var timeline_scroll : ScrollContainer = $AnimationContainer/TimelineContainer/TimelineScroll
onready var tag_scroll_container : ScrollContainer = $AnimationContainer/TimelineContainer/OpacityAndTagContainer/TagScroll
func _ready() -> void:
timeline_scroll.get_h_scrollbar().connect("value_changed", self, "_h_scroll_changed")
Global.animation_timer.wait_time = 1 / fps
func _h_scroll_changed(value : float) -> void:
# Let the main timeline ScrollContainer affect the tag ScrollContainer too
tag_scroll_container.get_child(0).rect_min_size.x = timeline_scroll.get_child(0).rect_size.x - 212
tag_scroll_container.scroll_horizontal = value
func add_frame() -> void:
var new_canvas : Canvas = load("res://src/Canvas.tscn").instance()
new_canvas.size = Global.canvas.size
new_canvas.frame = Global.canvases.size()
var new_canvases: Array = Global.canvases.duplicate()
new_canvases.append(new_canvas)
Global.undos += 1
Global.undo_redo.create_action("Add Frame")
Global.undo_redo.add_do_method(Global, "redo", [new_canvas])
Global.undo_redo.add_undo_method(Global, "undo", [new_canvas])
Global.undo_redo.add_do_property(Global, "canvases", new_canvases)
Global.undo_redo.add_do_property(Global, "canvas", new_canvas)
Global.undo_redo.add_do_property(Global, "current_frame", new_canvases.size() - 1)
for c in Global.canvases:
Global.undo_redo.add_do_property(c, "visible", false)
Global.undo_redo.add_undo_property(c, "visible", c.visible)
for l_i in range(Global.layers.size()):
if Global.layers[l_i][4]: # If the link button is pressed
Global.layers[l_i][5].append(new_canvas)
Global.undo_redo.add_undo_property(Global, "canvases", Global.canvases)
Global.undo_redo.add_undo_property(Global, "canvas", Global.canvas)
Global.undo_redo.add_undo_property(Global, "current_frame", Global.current_frame)
Global.undo_redo.commit_action()
func _on_DeleteFrame_pressed(frame := -1) -> void:
if Global.canvases.size() == 1:
return
if frame == -1:
frame = Global.current_frame
var canvas : Canvas = Global.canvases[frame]
var new_canvases := Global.canvases.duplicate()
new_canvases.erase(canvas)
var current_frame := Global.current_frame
if current_frame > 0 && current_frame == new_canvases.size(): # If it's the last frame
current_frame -= 1
var new_animation_tags := Global.animation_tags.duplicate(true)
# Loop through the tags to see if the frame is in one
for tag in new_animation_tags:
if frame + 1 >= tag[2] && frame + 1 <= tag[3]:
if tag[3] == tag[2]: # If we're deleting the only frame in the tag
new_animation_tags.erase(tag)
else:
tag[3] -= 1
elif frame + 1 < tag[2]:
tag[2] -= 1
tag[3] -= 1
# Check if one of the cels of the frame is linked
# if they are, unlink them too
# this prevents removed cels being kept in linked memory
var new_layers := Global.layers.duplicate(true)
for layer in new_layers:
for linked in layer[5]:
if linked == Global.canvases[frame]:
layer[5].erase(linked)
Global.undos += 1
Global.undo_redo.create_action("Remove Frame")
Global.undo_redo.add_do_property(Global, "canvases", new_canvases)
Global.undo_redo.add_do_property(Global, "canvas", new_canvases[current_frame])
Global.undo_redo.add_do_property(Global, "current_frame", current_frame)
Global.undo_redo.add_do_property(Global, "animation_tags", new_animation_tags)
Global.undo_redo.add_do_property(Global, "layers", new_layers)
# Change the frame value of the canvaseso on the right
# for example, if frame "3" was deleted, then "4" would have to become "3"
for i in range(frame, new_canvases.size()):
var c : Canvas = new_canvases[i]
Global.undo_redo.add_do_property(c, "frame", i)
Global.undo_redo.add_undo_property(c, "frame", c.frame)
Global.undo_redo.add_undo_property(Global, "canvases", Global.canvases)
Global.undo_redo.add_undo_property(Global, "canvas", canvas)
Global.undo_redo.add_undo_property(Global, "current_frame", Global.current_frame)
Global.undo_redo.add_undo_property(Global, "animation_tags", Global.animation_tags)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_do_method(Global, "redo", [canvas])
Global.undo_redo.add_undo_method(Global, "undo", [canvas])
Global.undo_redo.commit_action()
func _on_CopyFrame_pressed(frame := -1) -> void:
if frame == -1:
frame = Global.current_frame
var canvas : Canvas = Global.canvases[frame]
var new_canvas : Canvas = load("res://src/Canvas.tscn").instance()
new_canvas.size = Global.canvas.size
new_canvas.frame = Global.canvases.size()
var new_canvases := Global.canvases.duplicate()
new_canvases.insert(frame + 1, new_canvas)
for layer in canvas.layers: # Copy every layer
var sprite := Image.new()
sprite.copy_from(layer[0])
sprite.lock()
var tex := ImageTexture.new()
tex.create_from_image(sprite, 0)
new_canvas.layers.append([sprite, tex, layer[2]])
var new_animation_tags := Global.animation_tags.duplicate(true)
# Loop through the tags to see if the frame is in one
for tag in new_animation_tags:
if frame + 1 >= tag[2] && frame + 1 <= tag[3]:
tag[3] += 1
Global.undos += 1
Global.undo_redo.create_action("Add Frame")
Global.undo_redo.add_do_method(Global, "redo", [new_canvas])
Global.undo_redo.add_undo_method(Global, "undo", [new_canvas])
Global.undo_redo.add_do_property(Global, "canvases", new_canvases)
Global.undo_redo.add_do_property(Global, "canvas", new_canvas)
Global.undo_redo.add_do_property(Global, "current_frame", frame + 1)
Global.undo_redo.add_do_property(Global, "animation_tags", new_animation_tags)
for i in range(Global.layers.size()):
for child in Global.layers[i][3].get_children():
Global.undo_redo.add_do_property(child, "pressed", false)
Global.undo_redo.add_undo_property(child, "pressed", child.pressed)
for c in Global.canvases:
Global.undo_redo.add_do_property(c, "visible", false)
Global.undo_redo.add_undo_property(c, "visible", c.visible)
for i in range(frame, new_canvases.size()):
var c : Canvas = new_canvases[i]
Global.undo_redo.add_do_property(c, "frame", i)
Global.undo_redo.add_undo_property(c, "frame", c.frame)
Global.undo_redo.add_undo_property(Global, "canvases", Global.canvases)
Global.undo_redo.add_undo_property(Global, "canvas", Global.canvas)
Global.undo_redo.add_undo_property(Global, "current_frame", frame)
Global.undo_redo.add_undo_property(Global, "animation_tags", Global.animation_tags)
Global.undo_redo.commit_action()
func _on_FrameTagButton_pressed() -> void:
Global.tag_dialog.popup_centered()
func _on_OnionSkinning_pressed() -> void:
Global.onion_skinning = !Global.onion_skinning
Global.canvas.update()
var theme_type := Global.theme_type
if theme_type == "Gold":
theme_type = "Light"
var texture_button : TextureRect = Global.onion_skinning_button.get_child(0)
if Global.onion_skinning:
texture_button.texture = load("res://Assets/Graphics/%s Themes/Timeline/onion_skinning.png" % theme_type)
else:
texture_button.texture = load("res://Assets/Graphics/%s Themes/Timeline/onion_skinning_off.png" % theme_type)
func _on_OnionSkinningSettings_pressed() -> void:
$OnionSkinningSettings.popup(Rect2(Global.onion_skinning_button.rect_global_position.x - $OnionSkinningSettings.rect_size.x - 16, Global.onion_skinning_button.rect_global_position.y - 106, 136, 126))
func _on_LoopAnim_pressed() -> void:
var texture_button : TextureRect = Global.loop_animation_button.get_child(0)
var theme_type := Global.theme_type
if theme_type == "Gold":
theme_type = "Light"
match animation_loop:
0: # Make it loop
animation_loop = 1
texture_button.texture = load("res://Assets/Graphics/%s Themes/Timeline/loop.png" % theme_type)
Global.loop_animation_button.hint_tooltip = "Cycle loop"
1: # Make it ping-pong
animation_loop = 2
texture_button.texture = load("res://Assets/Graphics/%s Themes/Timeline/loop_pingpong.png" % theme_type)
Global.loop_animation_button.hint_tooltip = "Ping-pong loop"
2: # Make it stop
animation_loop = 0
texture_button.texture = load("res://Assets/Graphics/%s Themes/Timeline/loop_none.png" % theme_type)
Global.loop_animation_button.hint_tooltip = "No loop"
func _on_PlayForward_toggled(button_pressed : bool) -> void:
var theme_type := Global.theme_type
if theme_type == "Gold":
theme_type = "Light"
if button_pressed:
Global.play_forward.get_child(0).texture = load("res://Assets/Graphics/%s Themes/Timeline/pause.png" % theme_type)
else:
Global.play_forward.get_child(0).texture = load("res://Assets/Graphics/%s Themes/Timeline/play.png" % theme_type)
play_animation(button_pressed, true)
func _on_PlayBackwards_toggled(button_pressed : bool) -> void:
var theme_type := Global.theme_type
if theme_type == "Gold":
theme_type = "Light"
if button_pressed:
Global.play_backwards.get_child(0).texture = load("res://Assets/Graphics/%s Themes/Timeline/pause.png" % theme_type)
else:
Global.play_backwards.get_child(0).texture = load("res://Assets/Graphics/%s Themes/Timeline/play_backwards.png" % theme_type)
play_animation(button_pressed, false)
func _on_AnimationTimer_timeout() -> void:
if animation_forward:
if Global.current_frame < last_frame:
Global.current_frame += 1
else:
match animation_loop:
0: # No loop
Global.play_forward.pressed = false
Global.play_backwards.pressed = false
Global.animation_timer.stop()
1: # Cycle loop
Global.current_frame = first_frame
2: # Ping pong loop
animation_forward = false
_on_AnimationTimer_timeout()
else:
if Global.current_frame > first_frame:
Global.current_frame -= 1
else:
match animation_loop:
0: # No loop
Global.play_backwards.pressed = false
Global.play_forward.pressed = false
Global.animation_timer.stop()
1: # Cycle loop
Global.current_frame = last_frame
2: # Ping pong loop
animation_forward = true
_on_AnimationTimer_timeout()
func play_animation(play : bool, forward_dir : bool) -> void:
var theme_type := Global.theme_type
if theme_type == "Gold":
theme_type = "Light"
if forward_dir:
Global.play_backwards.disconnect("toggled", self, "_on_PlayBackwards_toggled")
Global.play_backwards.pressed = false
Global.play_backwards.get_child(0).texture = load("res://Assets/Graphics/%s Themes/Timeline/play_backwards.png" % theme_type)
Global.play_backwards.connect("toggled", self, "_on_PlayBackwards_toggled")
else:
Global.play_forward.disconnect("toggled", self, "_on_PlayForward_toggled")
Global.play_forward.pressed = false
Global.play_forward.get_child(0).texture = load("res://Assets/Graphics/%s Themes/Timeline/play.png" % theme_type)
Global.play_forward.connect("toggled", self, "_on_PlayForward_toggled")
if Global.canvases.size() == 1:
if forward_dir:
Global.play_forward.pressed = false
else:
Global.play_backwards.pressed = false
return
first_frame = 0
last_frame = Global.canvases.size() - 1
if Global.play_only_tags:
for tag in Global.animation_tags:
if Global.current_frame + 1 >= tag[2] && Global.current_frame + 1 <= tag[3]:
first_frame = tag[2] - 1
last_frame = min(Global.canvases.size() - 1, tag[3] - 1)
if play:
Global.animation_timer.wait_time = 1 / fps
Global.animation_timer.start()
animation_forward = forward_dir
else:
Global.animation_timer.stop()
func _on_NextFrame_pressed() -> void:
if Global.current_frame < Global.canvases.size() - 1:
Global.current_frame += 1
func _on_PreviousFrame_pressed() -> void:
if Global.current_frame > 0:
Global.current_frame -= 1
func _on_LastFrame_pressed() -> void:
Global.current_frame = Global.canvases.size() - 1
func _on_FirstFrame_pressed() -> void:
Global.current_frame = 0
func _on_FPSValue_value_changed(value : float) -> void:
fps = float(value)
Global.animation_timer.wait_time = 1 / fps
func _on_PastOnionSkinning_value_changed(value : float) -> void:
Global.onion_skinning_past_rate = int(value)
Global.canvas.update()
func _on_FutureOnionSkinning_value_changed(value : float) -> void:
Global.onion_skinning_future_rate = int(value)
Global.canvas.update()
func _on_BlueRedMode_toggled(button_pressed : bool) -> void:
Global.onion_skinning_blue_red = button_pressed
Global.canvas.update()
# Layer buttons
func add_layer(is_new := true) -> void:
var layer_name = null
if !is_new: # Clone layer
layer_name = Global.layers[Global.current_layer][0] + " (" + tr("copy") + ")"
var new_layers : Array = Global.layers.duplicate()
# Store [Layer name (0), Layer visibility boolean (1), Layer lock boolean (2), Frame container (3),
# will new frames be linked boolean (4), Array of linked frames (5)]
new_layers.append([layer_name, true, false, HBoxContainer.new(), false, []])
Global.undos += 1
Global.undo_redo.create_action("Add Layer")
for c in Global.canvases:
var new_layer := Image.new()
if is_new:
new_layer.create(c.size.x, c.size.y, false, Image.FORMAT_RGBA8)
else: # Clone layer
new_layer.copy_from(c.layers[Global.current_layer][0])
new_layer.lock()
var new_layer_tex := ImageTexture.new()
new_layer_tex.create_from_image(new_layer, 0)
var new_canvas_layers : Array = c.layers.duplicate()
# Store [Image, ImageTexture, Opacity]
new_canvas_layers.append([new_layer, new_layer_tex, 1])
Global.undo_redo.add_do_property(c, "layers", new_canvas_layers)
Global.undo_redo.add_undo_property(c, "layers", c.layers)
Global.undo_redo.add_do_property(Global, "current_layer", Global.current_layer + 1)
Global.undo_redo.add_do_property(Global, "layers", new_layers)
Global.undo_redo.add_undo_property(Global, "current_layer", Global.current_layer)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvas])
Global.undo_redo.add_do_method(Global, "redo", [Global.canvas])
Global.undo_redo.commit_action()
func _on_RemoveLayer_pressed() -> void:
var new_layers : Array = Global.layers.duplicate()
new_layers.remove(Global.current_layer)
Global.undos += 1
Global.undo_redo.create_action("Remove Layer")
if Global.current_layer > 0:
Global.undo_redo.add_do_property(Global, "current_layer", Global.current_layer - 1)
else:
Global.undo_redo.add_do_property(Global, "current_layer", Global.current_layer)
for c in Global.canvases:
var new_canvas_layers : Array = c.layers.duplicate()
new_canvas_layers.remove(Global.current_layer)
Global.undo_redo.add_do_property(c, "layers", new_canvas_layers)
Global.undo_redo.add_undo_property(c, "layers", c.layers)
Global.undo_redo.add_do_property(Global, "layers", new_layers)
Global.undo_redo.add_undo_property(Global, "current_layer", Global.current_layer)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_do_method(Global, "redo", [Global.canvas])
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvas])
Global.undo_redo.commit_action()
func change_layer_order(rate : int) -> void:
var change = Global.current_layer + rate
var new_layers : Array = Global.layers.duplicate()
var temp = new_layers[Global.current_layer]
new_layers[Global.current_layer] = new_layers[change]
new_layers[change] = temp
Global.undo_redo.create_action("Change Layer Order")
for c in Global.canvases:
var new_layers_canvas : Array = c.layers.duplicate()
var temp_canvas = new_layers_canvas[Global.current_layer]
new_layers_canvas[Global.current_layer] = new_layers_canvas[change]
new_layers_canvas[change] = temp_canvas
Global.undo_redo.add_do_property(c, "layers", new_layers_canvas)
Global.undo_redo.add_undo_property(c, "layers", c.layers)
Global.undo_redo.add_do_property(Global, "current_layer", change)
Global.undo_redo.add_do_property(Global, "layers", new_layers)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_undo_property(Global, "current_layer", Global.current_layer)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvas])
Global.undo_redo.add_do_method(Global, "redo", [Global.canvas])
Global.undo_redo.commit_action()
func _on_MergeDownLayer_pressed() -> void:
var new_layers : Array = Global.layers.duplicate(true)
Global.undos += 1
Global.undo_redo.create_action("Merge Layer")
for c in Global.canvases:
var new_layers_canvas : Array = c.layers.duplicate(true)
var selected_layer := Image.new()
selected_layer.copy_from(new_layers_canvas[Global.current_layer][0])
selected_layer.lock()
if c.layers[Global.current_layer][2] < 1: # If we have layer transparency
for xx in selected_layer.get_size().x:
for yy in selected_layer.get_size().y:
var pixel_color : Color = selected_layer.get_pixel(xx, yy)
var alpha : float = pixel_color.a * c.layers[Global.current_layer][2]
selected_layer.set_pixel(xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha))
var new_layer := Image.new()
new_layer.copy_from(c.layers[Global.current_layer - 1][0])
new_layer.lock()
c.blend_rect(new_layer, selected_layer, Rect2(c.position, c.size), Vector2.ZERO)
new_layers_canvas.remove(Global.current_layer)
if !selected_layer.is_invisible() and Global.layers[Global.current_layer - 1][5].size() > 1 and (c in Global.layers[Global.current_layer - 1][5]):
new_layers[Global.current_layer - 1][5].erase(c)
var tex := ImageTexture.new()
tex.create_from_image(new_layer, 0)
new_layers_canvas[Global.current_layer - 1][0] = new_layer
new_layers_canvas[Global.current_layer - 1][1] = tex
else:
Global.undo_redo.add_do_property(c.layers[Global.current_layer - 1][0], "data", new_layer.data)
Global.undo_redo.add_undo_property(c.layers[Global.current_layer - 1][0], "data", c.layers[Global.current_layer - 1][0].data)
Global.undo_redo.add_do_property(c, "layers", new_layers_canvas)
Global.undo_redo.add_undo_property(c, "layers", c.layers)
new_layers.remove(Global.current_layer)
Global.undo_redo.add_do_property(Global, "current_layer", Global.current_layer - 1)
Global.undo_redo.add_do_property(Global, "layers", new_layers)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_undo_property(Global, "current_layer", Global.current_layer)
Global.undo_redo.add_undo_method(Global, "undo", Global.canvases)
Global.undo_redo.add_do_method(Global, "redo", Global.canvases)
Global.undo_redo.commit_action()
func _on_OpacitySlider_value_changed(value) -> void:
Global.canvas.layers[Global.current_layer][2] = value / 100
Global.layer_opacity_slider.value = value
Global.layer_opacity_slider.value = value
Global.layer_opacity_spinbox.value = value
Global.canvas.update()
func _on_OnionSkinningSettings_popup_hide() -> void:
Global.can_draw = true

834
src/AnimationTimeline.tscn Normal file
View file

@ -0,0 +1,834 @@
[gd_scene load_steps=51 format=2]
[ext_resource path="res://src/AnimationTimeline.gd" type="Script" id=1]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/New_Layer.png" type="Texture" id=2]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/New_Layer_Hover.png" type="Texture" id=3]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Delete_Layer.png" type="Texture" id=4]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Delete_Layer_Hover.png" type="Texture" id=5]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Delete_Layer_Disabled.png" type="Texture" id=6]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Move_Up.png" type="Texture" id=7]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Move_Up_Hover.png" type="Texture" id=8]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Move_Up_Disabled.png" type="Texture" id=9]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Move_Down.png" type="Texture" id=10]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Move_Down_Hover.png" type="Texture" id=11]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Move_Down_Disabled.png" type="Texture" id=12]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Clone_Layer.png" type="Texture" id=13]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Clone_Layer_Hover.png" type="Texture" id=14]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Merge_Down.png" type="Texture" id=15]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Merge_Down_Hover.png" type="Texture" id=16]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Merge_Down_Disabled.png" type="Texture" id=17]
[ext_resource path="res://src/LayerButton.tscn" type="PackedScene" id=18]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/new_frame.png" type="Texture" id=19]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/remove_frame.png" type="Texture" id=20]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/go_to_first_frame.png" type="Texture" id=21]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/play.png" type="Texture" id=22]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/previous_frame.png" type="Texture" id=23]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/play_backwards.png" type="Texture" id=24]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/go_to_last_frame.png" type="Texture" id=25]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/next_frame.png" type="Texture" id=26]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/copy_frame.png" type="Texture" id=27]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/tag.png" type="Texture" id=28]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/onion_skinning_off.png" type="Texture" id=29]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/expandable.png" type="Texture" id=30]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/loop.png" type="Texture" id=31]
[ext_resource path="res://src/Dialogs/FrameTagDialog.tscn" type="PackedScene" id=42]
[sub_resource type="InputEventKey" id=1]
control = true
command = true
scancode = 16777229
[sub_resource type="ShortCut" id=2]
shortcut = SubResource( 1 )
[sub_resource type="InputEventKey" id=3]
control = true
command = true
scancode = 16777231
[sub_resource type="ShortCut" id=4]
shortcut = SubResource( 3 )
[sub_resource type="InputEventKey" id=5]
scancode = 16777247
[sub_resource type="ShortCut" id=6]
shortcut = SubResource( 5 )
[sub_resource type="InputEventKey" id=7]
scancode = 16777248
[sub_resource type="ShortCut" id=8]
shortcut = SubResource( 7 )
[sub_resource type="InputEventKey" id=9]
control = true
command = true
scancode = 16777233
[sub_resource type="ShortCut" id=10]
shortcut = SubResource( 9 )
[sub_resource type="InputEventKey" id=11]
control = true
command = true
scancode = 16777230
[sub_resource type="ShortCut" id=12]
shortcut = SubResource( 11 )
[sub_resource type="StyleBoxEmpty" id=13]
[sub_resource type="StyleBoxEmpty" id=14]
[sub_resource type="StyleBoxEmpty" id=15]
[sub_resource type="StyleBoxEmpty" id=16]
[sub_resource type="StyleBoxEmpty" id=17]
[sub_resource type="Theme" id=18]
HScrollBar/icons/decrement = null
HScrollBar/icons/decrement_highlight = null
HScrollBar/icons/increment = null
HScrollBar/icons/increment_highlight = null
HScrollBar/styles/grabber = SubResource( 13 )
HScrollBar/styles/grabber_highlight = SubResource( 14 )
HScrollBar/styles/grabber_pressed = SubResource( 15 )
HScrollBar/styles/scroll = SubResource( 16 )
HScrollBar/styles/scroll_focus = SubResource( 17 )
[node name="AnimationTimeline" type="Panel"]
margin_top = 438.0
margin_right = 704.0
margin_bottom = 620.0
rect_min_size = Vector2( 0, 200 )
size_flags_horizontal = 3
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AnimationContainer" type="HBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ForLayerButtons" type="VBoxContainer" parent="AnimationContainer"]
margin_right = 68.0
margin_bottom = 200.0
[node name="SpacerControl" type="Control" parent="AnimationContainer/ForLayerButtons"]
margin_right = 68.0
margin_bottom = 30.0
rect_min_size = Vector2( 0, 30 )
[node name="LayerButtons" type="GridContainer" parent="AnimationContainer/ForLayerButtons"]
margin_top = 34.0
margin_right = 68.0
margin_bottom = 138.0
size_flags_vertical = 0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="AddLayer" type="TextureButton" parent="AnimationContainer/ForLayerButtons/LayerButtons" groups=[
"UIButtons",
]]
margin_right = 32.0
margin_bottom = 32.0
hint_tooltip = "Create a new layer"
focus_mode = 0
mouse_default_cursor_shape = 2
texture_normal = ExtResource( 2 )
texture_hover = ExtResource( 3 )
[node name="RemoveLayer" type="TextureButton" parent="AnimationContainer/ForLayerButtons/LayerButtons" groups=[
"UIButtons",
]]
margin_left = 36.0
margin_right = 68.0
margin_bottom = 32.0
hint_tooltip = "Remove current layer"
focus_mode = 0
mouse_default_cursor_shape = 8
disabled = true
texture_normal = ExtResource( 4 )
texture_hover = ExtResource( 5 )
texture_disabled = ExtResource( 6 )
[node name="MoveUpLayer" type="TextureButton" parent="AnimationContainer/ForLayerButtons/LayerButtons" groups=[
"UIButtons",
]]
margin_top = 36.0
margin_right = 32.0
margin_bottom = 68.0
hint_tooltip = "Move up the current layer"
focus_mode = 0
mouse_default_cursor_shape = 8
disabled = true
texture_normal = ExtResource( 7 )
texture_hover = ExtResource( 8 )
texture_disabled = ExtResource( 9 )
[node name="MoveDownLayer" type="TextureButton" parent="AnimationContainer/ForLayerButtons/LayerButtons" groups=[
"UIButtons",
]]
margin_left = 36.0
margin_top = 36.0
margin_right = 68.0
margin_bottom = 68.0
hint_tooltip = "Move down the current layer"
focus_mode = 0
mouse_default_cursor_shape = 8
disabled = true
texture_normal = ExtResource( 10 )
texture_hover = ExtResource( 11 )
texture_disabled = ExtResource( 12 )
[node name="CloneLayer" type="TextureButton" parent="AnimationContainer/ForLayerButtons/LayerButtons" groups=[
"UIButtons",
]]
margin_top = 72.0
margin_right = 32.0
margin_bottom = 104.0
hint_tooltip = "Clone current layer"
focus_mode = 0
mouse_default_cursor_shape = 2
texture_normal = ExtResource( 13 )
texture_hover = ExtResource( 14 )
[node name="MergeDownLayer" type="TextureButton" parent="AnimationContainer/ForLayerButtons/LayerButtons" groups=[
"UIButtons",
]]
margin_left = 36.0
margin_top = 72.0
margin_right = 68.0
margin_bottom = 104.0
hint_tooltip = "Merge current layer with the one below"
focus_mode = 0
mouse_default_cursor_shape = 8
disabled = true
texture_normal = ExtResource( 15 )
texture_hover = ExtResource( 16 )
texture_disabled = ExtResource( 17 )
[node name="SpacerControl" type="Control" parent="AnimationContainer"]
margin_left = 72.0
margin_right = 88.0
margin_bottom = 200.0
rect_min_size = Vector2( 16, 0 )
[node name="TimelineContainer" type="VBoxContainer" parent="AnimationContainer"]
margin_left = 92.0
margin_right = 696.0
margin_bottom = 200.0
size_flags_horizontal = 3
[node name="SpacerControl" type="Control" parent="AnimationContainer/TimelineContainer"]
margin_right = 604.0
[node name="AnimationButtons" type="HBoxContainer" parent="AnimationContainer/TimelineContainer"]
margin_top = 4.0
margin_right = 604.0
margin_bottom = 28.0
rect_min_size = Vector2( 0, 24 )
[node name="CurrentFrame" type="Label" parent="AnimationContainer/TimelineContainer/AnimationButtons"]
margin_top = 5.0
margin_right = 150.0
margin_bottom = 19.0
rect_min_size = Vector2( 150, 0 )
text = "Current frame: 1/1"
[node name="AddFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons" groups=[
"UIButtons",
]]
margin_left = 154.0
margin_right = 174.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Add a new frame"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/AddFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -6.0
margin_top = -6.0
margin_right = 6.0
margin_bottom = 6.0
texture = ExtResource( 19 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="DeleteFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons" groups=[
"UIButtons",
]]
margin_left = 178.0
margin_right = 198.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Remove Frame"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/DeleteFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -6.0
margin_top = -1.0
margin_right = 6.0
margin_bottom = 1.0
size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource( 20 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CopyFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons" groups=[
"UIButtons",
]]
margin_left = 202.0
margin_right = 222.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Clone Frame"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/CopyFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -5.0
margin_top = -7.0
margin_right = 5.0
margin_bottom = 7.0
texture = ExtResource( 27 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="FrameTagButton" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons" groups=[
"UIButtons",
]]
margin_left = 226.0
margin_right = 246.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Manage frame tags"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/FrameTagButton"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -7.0
margin_top = -7.0
margin_right = 7.0
margin_bottom = 7.0
texture = ExtResource( 28 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PlaybackButtons" type="HBoxContainer" parent="AnimationContainer/TimelineContainer/AnimationButtons"]
margin_left = 286.0
margin_right = 426.0
margin_bottom = 24.0
size_flags_horizontal = 6
[node name="FirstFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons" groups=[
"UIButtons",
]]
margin_right = 20.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "FIRSTFRAME_HT"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
shortcut_in_tooltip = false
shortcut = SubResource( 2 )
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/FirstFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -5.5
margin_top = -6.0
margin_right = 5.5
margin_bottom = 6.0
texture = ExtResource( 21 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PreviousFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons" groups=[
"UIButtons",
]]
margin_left = 24.0
margin_right = 44.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "PREVIOUSFRAME_HT"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
shortcut_in_tooltip = false
shortcut = SubResource( 4 )
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/PreviousFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -5.5
margin_top = -6.0
margin_right = 5.5
margin_bottom = 6.0
texture = ExtResource( 23 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PlayBackwards" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons" groups=[
"UIButtons",
]]
margin_left = 48.0
margin_right = 68.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "PLAYBACKWARDS_HT"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
toggle_mode = true
shortcut_in_tooltip = false
shortcut = SubResource( 6 )
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/PlayBackwards"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -4.0
margin_top = -6.0
margin_right = 3.0
margin_bottom = 6.0
texture = ExtResource( 24 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PlayForward" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons" groups=[
"UIButtons",
]]
margin_left = 72.0
margin_right = 92.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "PLAYFORWARD_HT"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
toggle_mode = true
shortcut_in_tooltip = false
shortcut = SubResource( 8 )
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/PlayForward"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -3.5
margin_top = -6.0
margin_right = 3.5
margin_bottom = 6.0
texture = ExtResource( 22 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="NextFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons" groups=[
"UIButtons",
]]
margin_left = 96.0
margin_right = 116.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "NEXTFRAME_HT"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
shortcut_in_tooltip = false
shortcut = SubResource( 10 )
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/NextFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -5.5
margin_top = -6.0
margin_right = 5.5
margin_bottom = 6.0
texture = ExtResource( 26 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LastFrame" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons" groups=[
"UIButtons",
]]
margin_left = 120.0
margin_right = 140.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "LASTFRAME_HT"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
shortcut_in_tooltip = false
shortcut = SubResource( 12 )
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/LastFrame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -5.5
margin_top = -6.0
margin_right = 5.5
margin_bottom = 6.0
texture = ExtResource( 25 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LoopButtons" type="HBoxContainer" parent="AnimationContainer/TimelineContainer/AnimationButtons"]
margin_left = 466.0
margin_right = 604.0
margin_bottom = 24.0
size_flags_horizontal = 0
[node name="OnionSkinningSettings" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons" groups=[
"UIButtons",
]]
margin_right = 12.0
margin_bottom = 20.0
hint_tooltip = "Onion Skinning settings"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/OnionSkinningSettings"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -3.0
margin_top = -6.0
margin_right = 3.0
margin_bottom = 6.0
size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource( 30 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="OnionSkinning" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons" groups=[
"UIButtons",
]]
margin_left = 16.0
margin_right = 36.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Enable/disable Onion Skinning"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/OnionSkinning"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -7.0
margin_top = -7.0
margin_right = 7.0
margin_bottom = 7.0
size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource( 29 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LoopAnim" type="Button" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons" groups=[
"UIButtons",
]]
margin_left = 40.0
margin_right = 60.0
margin_bottom = 20.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Cycle loop"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/LoopAnim"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -7.0
margin_top = -7.0
margin_right = 7.0
margin_bottom = 7.0
texture = ExtResource( 31 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="FPSValue" type="SpinBox" parent="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons"]
margin_left = 64.0
margin_right = 138.0
margin_bottom = 24.0
hint_tooltip = "How many frames per second should the animation preview be?
The more FPS, the faster the animation plays."
mouse_default_cursor_shape = 2
min_value = 0.1
step = 0.1
value = 6.0
align = 1
suffix = "FPS"
[node name="HSeparator" type="HSeparator" parent="AnimationContainer/TimelineContainer"]
margin_top = 32.0
margin_right = 604.0
margin_bottom = 36.0
[node name="OpacityAndTagContainer" type="HBoxContainer" parent="AnimationContainer/TimelineContainer"]
margin_top = 40.0
margin_right = 604.0
margin_bottom = 72.0
custom_constants/separation = 2
[node name="OpacityContainer" type="HBoxContainer" parent="AnimationContainer/TimelineContainer/OpacityAndTagContainer"]
margin_right = 214.0
margin_bottom = 32.0
rect_min_size = Vector2( 214, 0 )
[node name="OpacityLabel" type="Label" parent="AnimationContainer/TimelineContainer/OpacityAndTagContainer/OpacityContainer"]
margin_right = 53.0
margin_bottom = 32.0
size_flags_horizontal = 0
size_flags_vertical = 1
text = "Opacity:"
valign = 1
[node name="OpacitySlider" type="HSlider" parent="AnimationContainer/TimelineContainer/OpacityAndTagContainer/OpacityContainer"]
margin_left = 57.0
margin_right = 136.0
margin_bottom = 32.0
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
size_flags_vertical = 1
value = 100.0
ticks_on_borders = true
[node name="OpacitySpinBox" type="SpinBox" parent="AnimationContainer/TimelineContainer/OpacityAndTagContainer/OpacityContainer"]
margin_left = 140.0
margin_top = 4.0
margin_right = 214.0
margin_bottom = 28.0
mouse_default_cursor_shape = 2
size_flags_vertical = 4
value = 100.0
align = 1
[node name="TagScroll" type="ScrollContainer" parent="AnimationContainer/TimelineContainer/OpacityAndTagContainer"]
margin_left = 216.0
margin_right = 604.0
margin_bottom = 32.0
rect_min_size = Vector2( 0, 32 )
size_flags_horizontal = 3
theme = SubResource( 18 )
scroll_vertical_enabled = false
[node name="TagContainer" type="Control" parent="AnimationContainer/TimelineContainer/OpacityAndTagContainer/TagScroll"]
[node name="TimelineScroll" type="ScrollContainer" parent="AnimationContainer/TimelineContainer"]
margin_top = 76.0
margin_right = 604.0
margin_bottom = 200.0
size_flags_vertical = 3
[node name="LayersAndFrames" type="HBoxContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll"]
margin_right = 252.0
margin_bottom = 124.0
size_flags_vertical = 3
[node name="LayerVBoxCont" type="VBoxContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames"]
margin_right = 212.0
margin_bottom = 124.0
[node name="LayerLabel" type="Label" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames/LayerVBoxCont"]
margin_right = 212.0
margin_bottom = 16.0
rect_min_size = Vector2( 0, 16 )
text = "Layers"
align = 1
valign = 1
[node name="LayersContainer" type="VBoxContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames/LayerVBoxCont"]
margin_top = 20.0
margin_right = 212.0
margin_bottom = 56.0
[node name="LayerContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames/LayerVBoxCont/LayersContainer" instance=ExtResource( 18 )]
margin_right = 212.0
[node name="FrameButtonsAndIds" type="VBoxContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames"]
margin_left = 216.0
margin_right = 252.0
margin_bottom = 124.0
[node name="FrameIDs" type="HBoxContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames/FrameButtonsAndIds"]
margin_right = 36.0
margin_bottom = 16.0
rect_min_size = Vector2( 0, 16 )
[node name="Label" type="Label" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames/FrameButtonsAndIds/FrameIDs"]
margin_top = 1.0
margin_right = 36.0
margin_bottom = 15.0
rect_min_size = Vector2( 36, 0 )
text = "1"
align = 1
[node name="FramesContainer" type="VBoxContainer" parent="AnimationContainer/TimelineContainer/TimelineScroll/LayersAndFrames/FrameButtonsAndIds"]
margin_top = 20.0
margin_right = 36.0
margin_bottom = 20.0
[node name="VSeparator" type="VSeparator" parent="AnimationContainer"]
margin_left = 700.0
margin_right = 704.0
margin_bottom = 200.0
[node name="AnimationTimer" type="Timer" parent="."]
[node name="OnionSkinningSettings" type="WindowDialog" parent="."]
rect_min_size = Vector2( 220, 126 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="OnionSkinningButtons" type="VBoxContainer" parent="OnionSkinningSettings"]
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
margin_left = 4.0
margin_top = -58.0
margin_right = -4.0
margin_bottom = 58.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="OnionSkinningPast" type="Label" parent="OnionSkinningSettings/OnionSkinningButtons"]
margin_right = 212.0
margin_bottom = 14.0
text = "Past Frames"
[node name="PastOnionSkinning" type="SpinBox" parent="OnionSkinningSettings/OnionSkinningButtons"]
margin_top = 18.0
margin_right = 212.0
margin_bottom = 42.0
mouse_default_cursor_shape = 2
min_value = 1.0
value = 1.0
align = 1
[node name="OnionSkinningFuture" type="Label" parent="OnionSkinningSettings/OnionSkinningButtons"]
margin_top = 46.0
margin_right = 212.0
margin_bottom = 60.0
text = "Future Frames"
[node name="FutureOnionSkinning" type="SpinBox" parent="OnionSkinningSettings/OnionSkinningButtons"]
margin_top = 64.0
margin_right = 212.0
margin_bottom = 88.0
mouse_default_cursor_shape = 2
min_value = 1.0
value = 1.0
align = 1
[node name="BlueRedMode" type="CheckBox" parent="OnionSkinningSettings/OnionSkinningButtons"]
margin_top = 92.0
margin_right = 212.0
margin_bottom = 116.0
mouse_default_cursor_shape = 2
text = "Blue-Red Mode"
[node name="FrameTagDialog" parent="." instance=ExtResource( 42 )]
[connection signal="pressed" from="AnimationContainer/ForLayerButtons/LayerButtons/AddLayer" to="." method="add_layer" binds= [ true ]]
[connection signal="pressed" from="AnimationContainer/ForLayerButtons/LayerButtons/RemoveLayer" to="." method="_on_RemoveLayer_pressed"]
[connection signal="pressed" from="AnimationContainer/ForLayerButtons/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [ 1 ]]
[connection signal="pressed" from="AnimationContainer/ForLayerButtons/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [ -1 ]]
[connection signal="pressed" from="AnimationContainer/ForLayerButtons/LayerButtons/CloneLayer" to="." method="add_layer" binds= [ false ]]
[connection signal="pressed" from="AnimationContainer/ForLayerButtons/LayerButtons/MergeDownLayer" to="." method="_on_MergeDownLayer_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/AddFrame" to="." method="add_frame"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/DeleteFrame" to="." method="_on_DeleteFrame_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/CopyFrame" to="." method="_on_CopyFrame_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/FrameTagButton" to="." method="_on_FrameTagButton_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/FirstFrame" to="." method="_on_FirstFrame_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/PreviousFrame" to="." method="_on_PreviousFrame_pressed"]
[connection signal="toggled" from="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/PlayBackwards" to="." method="_on_PlayBackwards_toggled"]
[connection signal="toggled" from="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/PlayForward" to="." method="_on_PlayForward_toggled"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/NextFrame" to="." method="_on_NextFrame_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/PlaybackButtons/LastFrame" to="." method="_on_LastFrame_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/OnionSkinningSettings" to="." method="_on_OnionSkinningSettings_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/OnionSkinning" to="." method="_on_OnionSkinning_pressed"]
[connection signal="pressed" from="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/LoopAnim" to="." method="_on_LoopAnim_pressed"]
[connection signal="value_changed" from="AnimationContainer/TimelineContainer/AnimationButtons/LoopButtons/FPSValue" to="." method="_on_FPSValue_value_changed"]
[connection signal="value_changed" from="AnimationContainer/TimelineContainer/OpacityAndTagContainer/OpacityContainer/OpacitySlider" to="." method="_on_OpacitySlider_value_changed"]
[connection signal="value_changed" from="AnimationContainer/TimelineContainer/OpacityAndTagContainer/OpacityContainer/OpacitySpinBox" to="." method="_on_OpacitySlider_value_changed"]
[connection signal="timeout" from="AnimationTimer" to="." method="_on_AnimationTimer_timeout"]
[connection signal="popup_hide" from="OnionSkinningSettings" to="." method="_on_OnionSkinningSettings_popup_hide"]
[connection signal="value_changed" from="OnionSkinningSettings/OnionSkinningButtons/PastOnionSkinning" to="." method="_on_PastOnionSkinning_value_changed"]
[connection signal="value_changed" from="OnionSkinningSettings/OnionSkinningButtons/FutureOnionSkinning" to="." method="_on_FutureOnionSkinning_value_changed"]
[connection signal="toggled" from="OnionSkinningSettings/OnionSkinningButtons/BlueRedMode" to="." method="_on_BlueRedMode_toggled"]

1160
src/Autoload/Global.gd Normal file

File diff suppressed because it is too large Load diff

336
src/Autoload/Import.gd Normal file
View file

@ -0,0 +1,336 @@
extends Node
# Get hold of the brushes, including random brushes (subdirectories and % files
# in them, non % files get loaded independently.) nyaaa
# Returns a list of [
# [non random single png files in the root subdir],
# {
# map of subdirectories to lists of files for
# the randomised brush - if a directory contains no
# randomised files then it is not included in this.
# },
# {
# map of subdirectories to lists of files inside of them
# that are not for randomised brushes.
# }
# ]
# The separation of nonrandomised and randomised files
# in subdirectories allows different XDG_DATA_DIR overriding
# for each nyaa.
#
# Returns null if the directory gave an error opening.
#
func get_brush_files_from_directory(directory: String): # -> Array
var base_png_files := [] # list of files in the base directory
var subdirectories := [] # list of subdirectories to process.
var randomised_subdir_files_map : Dictionary = {}
var nonrandomised_subdir_files_map : Dictionary = {}
var main_directory : Directory = Directory.new()
var err := main_directory.open(directory)
if err != OK:
return null
# Build first the list of base png files and all subdirectories to
# scan later (skip navigational . and ..)
main_directory.list_dir_begin(true)
var fname : String = main_directory.get_next()
while fname != "":
if main_directory.current_is_dir():
subdirectories.append(fname)
else: # Filter for pngs
if fname.get_extension().to_lower() == "png":
base_png_files.append(fname)
# go to next
fname = main_directory.get_next()
main_directory.list_dir_end()
# Now we iterate over subdirectories!
for subdirectory in subdirectories:
var the_directory : Directory = Directory.new()
# Holds names of files that make this
# a component of a randomised brush ^.^
var randomised_files := []
# Non-randomise-indicated image files
var non_randomised_files := []
the_directory.open(directory.plus_file(subdirectory))
the_directory.list_dir_begin(true)
var curr_file := the_directory.get_next()
while curr_file != "":
# only do stuff if we are actually dealing with a file
# and png one at that nya
if !the_directory.current_is_dir() and curr_file.get_extension().to_lower() == "png":
# if we are a random element, add
if "%" in curr_file:
randomised_files.append(curr_file)
else:
non_randomised_files.append(curr_file)
curr_file = the_directory.get_next()
the_directory.list_dir_end()
# Add these to the maps nyaa
if len(randomised_files) > 0:
randomised_subdir_files_map[subdirectory] = randomised_files
if len(non_randomised_files) > 0:
nonrandomised_subdir_files_map[subdirectory] = non_randomised_files
# We are done generating the maps!
return [base_png_files, randomised_subdir_files_map, nonrandomised_subdir_files_map]
# Add a randomised brush from the given list of files as a source.
# The tooltip name is what shows up on the tooltip
# and is probably in this case the name of the containing
# randomised directory.
func add_randomised_brush(fpaths : Array, tooltip_name : String) -> void:
# Attempt to load the images from the file paths.
var loaded_images : Array = []
for filen in fpaths:
var image := Image.new()
var err := image.load(filen)
if err == OK:
image.convert(Image.FORMAT_RGBA8)
loaded_images.append(image)
# If any images were successfully loaded, then
# we create the randomised brush button, copied
# from find_brushes.
if len(loaded_images) > 0: # actually have images
# to use.
# take initial image...
var first_image : Image = loaded_images.pop_front()
# The index which this random brush will be at
var next_random_brush_index := Global.file_brush_container.get_child_count()
Global.custom_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.
# Taken, again, from find_brushes
func add_plain_brush(path: String, tooltip_name: String) -> void:
var image := Image.new()
var err := image.load(path)
if err != OK:
return
# do the standard conversion thing...
image.convert(Image.FORMAT_RGBA8)
Global.custom_brushes.append(image)
Global.create_brush_button(image, Global.Brush_Types.FILE, tooltip_name)
# Import brushes, in priority order, from the paths in question in priority order
# i.e. with an override system
# We use a very particular override system here where, for randomised brushes
# the directories containing them get overridden, but for nonrandomised files
# (including in directories containing randomised components too), the override
# is on a file-by-file basis nyaaaa ^.^
func import_brushes(priority_ordered_search_path: Array) -> void:
# Maps for files in the base directory (name : true)
var processed_basedir_paths : Dictionary = {}
var randomised_brush_subdirectories : Dictionary = {}
# Map from a subdirectory to a map similar to processed_basedir_files
# i.e. once a filename has been dealt with, set it to true.
var processed_subdir_paths : Dictionary = {}
# Sets of results of get_brush_files_from_directory
var all_available_paths : Array = []
for directory in priority_ordered_search_path:
all_available_paths.append(get_brush_files_from_directory(directory))
# Now to process. Note these are in order of the
# priority, as intended nyaa :)
for i in range(len(all_available_paths)):
var available_brush_file_information = all_available_paths[i]
var current_main_directory: String = priority_ordered_search_path[i]
if available_brush_file_information != null:
# The brush files in the main directory
var main_directory_file_paths : Array = available_brush_file_information[0]
# The subdirectory/list-of-randomised-brush-files
# map for this directory
var randomised_brush_subdirectory_map : Dictionary = available_brush_file_information[1]
# Map for subdirectories to non-randomised-brush files nyaa
var nonrandomised_brush_subdirectory_map : Dictionary = available_brush_file_information[2]
# Iterate over components and do stuff with them! nyaa
# first for the main directory path...
for subfile in main_directory_file_paths:
if not (subfile in processed_basedir_paths):
add_plain_brush(
current_main_directory.plus_file(subfile),
subfile.get_basename()
)
processed_basedir_paths[subfile] = true
# Iterate over the randomised brush files nyaa
for randomised_subdir in randomised_brush_subdirectory_map:
if not (randomised_subdir in randomised_brush_subdirectories):
var full_paths := []
# glue the proper path onto the single file names in the
# random brush directory data system, so they can be
# opened nya
for non_extended_path in randomised_brush_subdirectory_map[randomised_subdir]:
full_paths.append(current_main_directory.plus_file(
randomised_subdir
).plus_file(
non_extended_path
))
# Now load!
add_randomised_brush(full_paths, randomised_subdir)
# and mark that we are done in the overall map ^.^
randomised_brush_subdirectories[randomised_subdir] = true
# Now to iterate over the nonrandom brush files inside directories
for nonrandomised_subdir in nonrandomised_brush_subdirectory_map:
# initialise the set-map for this one if not already present :)
if not (nonrandomised_subdir in processed_subdir_paths):
processed_subdir_paths[nonrandomised_subdir] = {}
# Get the paths within this subdirectory to check if they are
# processed or not and if not, then process them.
var relpaths_of_contained_nonrandom_brushes : Array = nonrandomised_brush_subdirectory_map[nonrandomised_subdir]
for relative_path in relpaths_of_contained_nonrandom_brushes:
if not (relative_path in processed_subdir_paths[nonrandomised_subdir]):
# We are not yet processed
var full_path : String = current_main_directory.plus_file(
nonrandomised_subdir
).plus_file(
relative_path
)
# Add the path with the tooltip including the directory
add_plain_brush(full_path, nonrandomised_subdir.plus_file(
relative_path
).get_basename())
# Mark this as a processed relpath
processed_subdir_paths[nonrandomised_subdir][relative_path] = true
Global.brushes_from_files = Global.custom_brushes.size()
func import_patterns(priority_ordered_search_path: Array) -> void:
for path in priority_ordered_search_path:
var pattern_list := []
var dir := Directory.new()
dir.open(path)
dir.list_dir_begin()
var curr_file := dir.get_next()
while curr_file != "":
if curr_file.get_extension().to_lower() == "png":
pattern_list.append(curr_file)
curr_file = dir.get_next()
dir.list_dir_end()
for pattern in pattern_list:
var image := Image.new()
var err := image.load(path.plus_file(pattern))
if err == OK:
image.convert(Image.FORMAT_RGBA8)
Global.patterns.append(image)
var pattern_button : BaseButton = load("res://src/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 = pattern
pattern_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
Global.patterns_popup.get_node("ScrollContainer/PatternContainer").add_child(pattern_button)
if Global.patterns.size() > 0:
var image_size = Global.patterns[0].get_size()
Global.pattern_left_image = Global.patterns[0]
var pattern_left_tex := ImageTexture.new()
pattern_left_tex.create_from_image(Global.pattern_left_image, 0)
Global.left_fill_pattern_container.get_child(0).get_child(0).texture = pattern_left_tex
Global.left_fill_pattern_container.get_child(2).get_child(1).max_value = image_size.x - 1
Global.left_fill_pattern_container.get_child(3).get_child(1).max_value = image_size.y - 1
Global.pattern_right_image = Global.patterns[0]
var pattern_right_tex := ImageTexture.new()
pattern_right_tex.create_from_image(Global.pattern_right_image, 0)
Global.right_fill_pattern_container.get_child(0).get_child(0).texture = pattern_right_tex
Global.right_fill_pattern_container.get_child(2).get_child(1).max_value = image_size.x - 1
Global.right_fill_pattern_container.get_child(3).get_child(1).max_value = image_size.y - 1
func import_gpl(path : String) -> Palette:
var result : Palette = null
var file = File.new()
if file.file_exists(path):
file.open(path, File.READ)
var text = file.get_as_text()
var lines = text.split('\n')
var line_number := 0
var comments := ""
for line in lines:
# Check if valid Gimp Palette Library file
if line_number == 0:
if line != "GIMP Palette":
break
else:
result = Palette.new()
var name_start = path.find_last('/') + 1
var name_end = path.find_last('.')
if name_end > name_start:
result.name = path.substr(name_start, name_end - name_start)
# Comments
if line.begins_with('#'):
comments += line.trim_prefix('#') + '\n'
pass
elif line_number > 0 && line.length() >= 12:
line = line.replace("\t", " ")
var color_data : PoolStringArray = line.split(" ", false, 4)
var red : float = color_data[0].to_float() / 255.0
var green : float = color_data[1].to_float() / 255.0
var blue : float = color_data[2].to_float() / 255.0
var color = Color(red, green, blue)
result.add_color(color, color_data[3])
line_number += 1
if result:
result.comments = comments
file.close()
return result
func import_png_palette(path: String) -> Palette:
var result: Palette = null
var image := Image.new()
var err := image.load(path)
if err != OK: # An error occured
return null
var height: int = image.get_height()
var width: int = image.get_width()
result = Palette.new()
# Iterate all pixels and store unique colors to palete
image.lock()
for y in range(0, height):
for x in range(0, width):
var color: Color = image.get_pixel(x, y)
if not result.has_color(color):
result.add_color(color, "#" + color.to_html())
image.unlock()
var name_start = path.find_last('/') + 1
var name_end = path.find_last('.')
if name_end > name_start:
result.name = path.substr(name_start, name_end - name_start)
return result

313
src/Autoload/OpenSave.gd Normal file
View file

@ -0,0 +1,313 @@
extends Node
var current_save_path := ""
# Stores a filename of a backup file in user:// until user saves manually
var backup_save_path = ""
var default_autosave_interval := 5 # Minutes
onready var autosave_timer : Timer
func _ready() -> void:
autosave_timer = Timer.new()
autosave_timer.one_shot = false
autosave_timer.process_mode = Timer.TIMER_PROCESS_IDLE
autosave_timer.connect("timeout", self, "_on_Autosave_timeout")
add_child(autosave_timer)
set_autosave_interval(default_autosave_interval)
toggle_autosave(false) # Gets started from preferences dialog
func open_pxo_file(path : String, untitled_backup : bool = false) -> void:
var file := File.new()
var err := file.open_compressed(path, File.READ, File.COMPRESSION_ZSTD)
if err == ERR_FILE_UNRECOGNIZED:
err = file.open(path, File.READ) # If the file is not compressed open it raw (pre-v0.7)
if err != OK:
Global.notification_label("File failed to open")
file.close()
return
var file_version := file.get_line() # Example, "v0.6"
var file_major_version = int(file_version.substr(1, 1))
var file_minor_version = int(file_version.substr(3, 1))
if file_major_version == 0 and file_minor_version < 5:
Global.notification_label("File is from an older version of Pixelorama, as such it might not work properly")
var frame := 0
Global.layers.clear()
if file_major_version >= 0 and file_minor_version > 6:
var global_layer_line := file.get_line()
while global_layer_line == ".":
var layer_name := file.get_line()
var layer_visibility := file.get_8()
var layer_lock := file.get_8()
var layer_new_frames_linked := file.get_8()
var linked_frames = file.get_var()
# Store [Layer name (0), Layer visibility boolean (1), Layer lock boolean (2), Frame container (3),
# will new frames be linked boolean (4), Array of linked frames (5)]
Global.layers.append([layer_name, layer_visibility, layer_lock, HBoxContainer.new(), layer_new_frames_linked, linked_frames])
global_layer_line = file.get_line()
var frame_line := file.get_line()
Global.clear_canvases()
while frame_line == "--": # Load frames
var canvas : Canvas = load("res://src/Canvas.tscn").instance()
Global.canvas = canvas
var width := file.get_16()
var height := file.get_16()
var layer_line := file.get_line()
while layer_line == "-": # Load layers
var buffer := file.get_buffer(width * height * 4)
if file_major_version == 0 and file_minor_version < 7:
var layer_name_old_version = file.get_line()
if frame == 0:
# Store [Layer name (0), Layer visibility boolean (1), Layer lock boolean (2), Frame container (3),
# will new frames be linked boolean (4), Array of linked frames (5)]
Global.layers.append([layer_name_old_version, true, false, HBoxContainer.new(), false, []])
var layer_transparency := 1.0
if file_major_version >= 0 and file_minor_version > 5:
layer_transparency = file.get_float()
var image := Image.new()
image.create_from_data(width, height, false, Image.FORMAT_RGBA8, buffer)
image.lock()
var tex := ImageTexture.new()
tex.create_from_image(image, 0)
canvas.layers.append([image, tex, layer_transparency])
layer_line = file.get_line()
var guide_line := file.get_line() # "guideline" no pun intended
while guide_line == "|": # Load guides
var guide := Guide.new()
guide.default_color = Color.purple
guide.type = file.get_8()
if guide.type == guide.Types.HORIZONTAL:
guide.add_point(Vector2(-99999, file.get_16()))
guide.add_point(Vector2(99999, file.get_16()))
else:
guide.add_point(Vector2(file.get_16(), -99999))
guide.add_point(Vector2(file.get_16(), 99999))
guide.has_focus = false
canvas.add_child(guide)
guide_line = file.get_line()
canvas.size = Vector2(width, height)
Global.canvases.append(canvas)
canvas.frame = frame
Global.canvas_parent.add_child(canvas)
frame_line = file.get_line()
frame += 1
Global.canvases = Global.canvases # Just to call Global.canvases_changed
Global.current_frame = frame - 1
Global.layers = Global.layers # Just to call Global.layers_changed
# Load tool options
Global.left_color_picker.color = file.get_var()
Global.right_color_picker.color = file.get_var()
Global.left_brush_size = file.get_8()
Global.left_brush_size_edit.value = Global.left_brush_size
Global.right_brush_size = file.get_8()
Global.right_brush_size_edit.value = Global.right_brush_size
if file_major_version == 0 and file_minor_version < 7:
var left_palette = file.get_var()
var right_palette = file.get_var()
for color in left_palette:
Global.left_color_picker.get_picker().add_preset(color)
for color in right_palette:
Global.right_color_picker.get_picker().add_preset(color)
# Load custom brushes
Global.custom_brushes.resize(Global.brushes_from_files)
Global.remove_brush_buttons()
var brush_line := file.get_line()
while brush_line == "/":
var b_width := file.get_16()
var b_height := file.get_16()
var buffer := file.get_buffer(b_width * b_height * 4)
var image := Image.new()
image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer)
Global.custom_brushes.append(image)
Global.create_brush_button(image)
brush_line = file.get_line()
if file_major_version >= 0 and file_minor_version > 6:
var tag_line := file.get_line()
while tag_line == ".T/":
var tag_name := file.get_line()
var tag_color : Color = file.get_var()
var tag_from := file.get_8()
var tag_to := file.get_8()
Global.animation_tags.append([tag_name, tag_color, tag_from, tag_to])
Global.animation_tags = Global.animation_tags # To execute animation_tags_changed()
tag_line = file.get_line()
file.close()
if not untitled_backup:
# Untitled backup should not change window title and save path
current_save_path = path
Global.window_title = path.get_file() + " - Pixelorama"
Global.project_has_changed = false
func save_pxo_file(path : String, autosave : bool) -> void:
var file := File.new()
var err := file.open_compressed(path, File.WRITE, File.COMPRESSION_ZSTD)
if err == OK:
# Store Pixelorama version
file.store_line(ProjectSettings.get_setting("application/config/Version"))
# Store Global layers
for layer in Global.layers:
file.store_line(".")
file.store_line(layer[0]) # Layer name
file.store_8(layer[1]) # Layer visibility
file.store_8(layer[2]) # Layer lock
file.store_8(layer[4]) # Future frames linked
file.store_var(layer[5]) # Linked frames
file.store_line("END_GLOBAL_LAYERS")
# Store frames
for canvas in Global.canvases:
file.store_line("--")
file.store_16(canvas.size.x)
file.store_16(canvas.size.y)
for layer in canvas.layers: # Store canvas layers
file.store_line("-")
file.store_buffer(layer[0].get_data())
file.store_float(layer[2]) # Layer transparency
file.store_line("END_LAYERS")
# Store guides
for child in canvas.get_children():
if child is Guide:
file.store_line("|")
file.store_8(child.type)
if child.type == child.Types.HORIZONTAL:
file.store_16(child.points[0].y)
file.store_16(child.points[1].y)
else:
file.store_16(child.points[1].x)
file.store_16(child.points[0].x)
file.store_line("END_GUIDES")
file.store_line("END_FRAMES")
# Save tool options
var left_color : Color = Global.left_color_picker.color
var right_color : Color = Global.right_color_picker.color
var left_brush_size : int = Global.left_brush_size
var right_brush_size : int = Global.right_brush_size
file.store_var(left_color)
file.store_var(right_color)
file.store_8(left_brush_size)
file.store_8(right_brush_size)
# Save custom brushes
for i in range(Global.brushes_from_files, Global.custom_brushes.size()):
var brush = Global.custom_brushes[i]
file.store_line("/")
file.store_16(brush.get_size().x)
file.store_16(brush.get_size().y)
file.store_buffer(brush.get_data())
file.store_line("END_BRUSHES")
# Store animation tags
for tag in Global.animation_tags:
file.store_line(".T/")
file.store_line(tag[0]) # Tag name
file.store_var(tag[1]) # Tag color
file.store_8(tag[2]) # Tag "from", the first frame
file.store_8(tag[3]) # Tag "to", the last frame
file.store_line("END_FRAME_TAGS")
file.close()
if Global.project_has_changed and not autosave:
Global.project_has_changed = false
if autosave:
Global.notification_label("File autosaved")
else:
# First remove backup then set current save path
remove_backup()
current_save_path = path
Global.notification_label("File saved")
if backup_save_path == "":
Global.window_title = path.get_file() + " - Pixelorama"
else:
Global.notification_label("File failed to save")
func toggle_autosave(enable : bool) -> void:
if enable:
autosave_timer.start()
else:
autosave_timer.stop()
func set_autosave_interval(interval : float) -> void:
autosave_timer.wait_time = interval * 60 # Interval parameter is in minutes, wait_time is seconds
autosave_timer.start()
func _on_Autosave_timeout() -> void:
if backup_save_path == "":
# Create a new backup file if it doesn't exist yet
backup_save_path = "user://backup-" + String(OS.get_unix_time())
store_backup_path()
save_pxo_file(backup_save_path, true)
# Backup paths are stored in two ways:
# 1) User already manually saved and defined a save path -> {current_save_path, backup_save_path}
# 2) User didn't manually saved, "untitled" backup is stored -> {backup_save_path, backup_save_path}
func store_backup_path() -> void:
if current_save_path != "":
# Remove "untitled" backup if it existed on this project instance
if Global.config_cache.has_section_key("backups", backup_save_path):
Global.config_cache.erase_section_key("backups", backup_save_path)
Global.config_cache.set_value("backups", current_save_path, backup_save_path)
else:
Global.config_cache.set_value("backups", backup_save_path, backup_save_path)
Global.config_cache.save("user://cache.ini")
func remove_backup() -> void:
# Remove backup file
if backup_save_path != "":
if current_save_path != "":
remove_backup_by_path(current_save_path, backup_save_path)
else:
# If manual save was not yet done - remove "untitled" backup
remove_backup_by_path(backup_save_path, backup_save_path)
backup_save_path = ""
func remove_backup_by_path(project_path : String, backup_path : String) -> void:
Directory.new().remove(backup_path)
Global.config_cache.erase_section_key("backups", project_path)
Global.config_cache.save("user://cache.ini")
func reload_backup_file(project_path : String, backup_path : String) -> void:
# If project path is the same as backup save path -> the backup was untitled
open_pxo_file(backup_path, project_path == backup_path)
backup_save_path = backup_path
if project_path != backup_path:
current_save_path = project_path
Global.window_title = project_path.get_file() + " - Pixelorama(*)"
Global.project_has_changed = true
Global.notification_label("Backup reloaded")

108
src/BrushButton.gd Normal file
View file

@ -0,0 +1,108 @@
extends BaseButton
signal brush_selected
export var brush_type = 0 # Global.Brush_Types.PIXEL
export var custom_brush_index := -3
var random_brushes := []
func _on_BrushButton_pressed() -> void:
# Delete the brush on middle mouse press
if Input.is_action_just_released("middle_mouse"):
_on_DeleteButton_pressed()
return
# Change left brush
if Global.brush_type_window_position == "left":
Global.current_left_brush_type = brush_type
Global.custom_left_brush_index = custom_brush_index
if custom_brush_index > -1: # Custom brush
if Global.current_left_tool == "Pencil":
Global.left_color_interpolation_container.visible = true
# if hint_tooltip == "":
# Global.left_brush_type_label.text = tr("Custom brush")
# else:
# Global.left_brush_type_label.text = tr("Brush:") + " %s" % hint_tooltip
elif custom_brush_index == -3: # Pixel brush
Global.left_color_interpolation_container.visible = false
# Global.left_brush_type_label.text = tr("Brush: Pixel")
elif custom_brush_index == -2: # Circle brush
Global.left_color_interpolation_container.visible = false
# Global.left_brush_type_label.text = tr("Brush: Circle")
elif custom_brush_index == -1: # Filled Circle brush
Global.left_color_interpolation_container.visible = false
# Global.left_brush_type_label.text = tr("Brush: Filled Circle")
Global.update_left_custom_brush()
emit_signal("brush_selected")
else: # Change right brush
Global.current_right_brush_type = brush_type
Global.custom_right_brush_index = custom_brush_index
if custom_brush_index > -1:
if Global.current_right_tool == "Pencil":
Global.right_color_interpolation_container.visible = true
# if hint_tooltip == "":
# Global.right_brush_type_label.text = tr("Custom brush")
# else:
# Global.right_brush_type_label.text = tr("Brush:") + " %s" % hint_tooltip
elif custom_brush_index == -3: # Pixel brush
Global.right_color_interpolation_container.visible = false
# Global.right_brush_type_label.text = tr("Brush: Pixel")
elif custom_brush_index == -2: # Circle brush
Global.right_color_interpolation_container.visible = false
# Global.right_brush_type_label.text = tr("Brush: Circle")
elif custom_brush_index == -1: # Filled Circle brush
Global.right_color_interpolation_container.visible = false
# Global.right_brush_type_label.text = tr("Brush: Filled Circle")
Global.update_right_custom_brush()
emit_signal("brush_selected")
func _on_DeleteButton_pressed() -> void:
if brush_type == Global.Brush_Types.CUSTOM:
if Global.custom_left_brush_index == custom_brush_index:
Global.custom_left_brush_index = -3
Global.current_left_brush_type = Global.Brush_Types.PIXEL
# Global.left_brush_type_label.text = "Brush: Pixel"
Global.update_left_custom_brush()
if Global.custom_right_brush_index == custom_brush_index:
Global.custom_right_brush_index = -3
Global.current_right_brush_type = Global.Brush_Types.PIXEL
# Global.right_brush_type_label.text = "Brush: Pixel"
Global.update_right_custom_brush()
var project_brush_index = custom_brush_index - Global.brushes_from_files
Global.undos += 1
Global.undo_redo.create_action("Delete Custom Brush")
for i in range(project_brush_index, Global.project_brush_container.get_child_count()):
var bb = Global.project_brush_container.get_child(i)
if Global.custom_left_brush_index == bb.custom_brush_index:
Global.custom_left_brush_index -= 1
if Global.custom_right_brush_index == bb.custom_brush_index:
Global.custom_right_brush_index -= 1
Global.undo_redo.add_do_property(bb, "custom_brush_index", bb.custom_brush_index - 1)
Global.undo_redo.add_undo_property(bb, "custom_brush_index", bb.custom_brush_index)
var custom_brushes: Array = Global.custom_brushes.duplicate()
custom_brushes.remove(custom_brush_index)
Global.undo_redo.add_do_property(Global, "custom_brushes", custom_brushes)
Global.undo_redo.add_undo_property(Global, "custom_brushes", Global.custom_brushes)
Global.undo_redo.add_do_method(Global, "redo_custom_brush", self)
Global.undo_redo.add_undo_method(Global, "undo_custom_brush", self)
Global.undo_redo.commit_action()
func _on_BrushButton_mouse_entered() -> void:
if brush_type == Global.Brush_Types.CUSTOM:
$DeleteButton.visible = true
func _on_BrushButton_mouse_exited() -> void:
if brush_type == Global.Brush_Types.CUSTOM:
$DeleteButton.visible = false

58
src/BrushButton.tscn Normal file

File diff suppressed because one or more lines are too long

161
src/CameraMovement.gd Normal file
View file

@ -0,0 +1,161 @@
extends Camera2D
var tween : Tween
var zoom_min := Vector2(0.005, 0.005)
var zoom_max := Vector2.ONE
var viewport_container : ViewportContainer
var mouse_pos := Vector2.ZERO
var drag := false
func _ready() -> void:
viewport_container = get_parent().get_parent()
tween = Tween.new()
add_child(tween)
tween.connect("tween_step", self, "_on_tween_step")
# Get the speed multiplier for when you've pressed
# a movement key for the given amount of time
func dir_move_zoom_multiplier(press_time : float) -> float:
if press_time < 0:
return 0.0
if Input.is_key_pressed(KEY_SHIFT) and Input.is_key_pressed(KEY_CONTROL) :
return Global.high_speed_move_rate
elif Input.is_key_pressed(KEY_SHIFT):
return Global.medium_speed_move_rate
elif !Input.is_key_pressed(KEY_CONTROL):
# control + right/left is used to move frames so
# we do this check to ensure that there is no conflict
return Global.low_speed_move_rate
else:
return 0.0
func reset_dir_move_time(direction) -> void:
Global.key_move_press_time[direction] = 0.0
const key_move_action_names := ["ui_up", "ui_down", "ui_left", "ui_right"]
# Check if an event is a ui_up/down/left/right event-press :)
func is_action_direction_pressed(event : InputEvent, allow_echo: bool = true) -> bool:
for action in key_move_action_names:
if event.is_action_pressed(action, allow_echo):
return true
return false
# Check if an event is a ui_up/down/left/right event release nya
func is_action_direction_released(event: InputEvent) -> bool:
for action in key_move_action_names:
if event.is_action_released(action):
return true
return false
# get the Direction associated with the event.
# if not a direction event return null
func get_action_direction(event: InputEvent): # -> Optional[Direction]
if event.is_action("ui_up"):
return Global.Direction.UP
elif event.is_action("ui_down"):
return Global.Direction.DOWN
elif event.is_action("ui_left"):
return Global.Direction.LEFT
elif event.is_action("ui_right"):
return Global.Direction.RIGHT
return null
# Holds sign multipliers for the given directions nyaa
# (per the indices in Global.gd defined by Direction)
# UP, DOWN, LEFT, RIGHT in that order
const directional_sign_multipliers := [
Vector2(0.0, -1.0),
Vector2(0.0, 1.0),
Vector2(-1.0, 0.0),
Vector2(1.0, 0.0)
]
# Process an action event for a pressed direction
# action
func process_direction_action_pressed(event: InputEvent) -> void:
var dir = get_action_direction(event)
if dir == null:
return
var increment := get_process_delta_time()
# Count the total time we've been doing this ^.^
Global.key_move_press_time[dir] += increment
var this_direction_press_time : float = Global.key_move_press_time[dir]
var move_speed := dir_move_zoom_multiplier(this_direction_press_time)
offset = offset + move_speed * increment * directional_sign_multipliers[dir] * zoom
# Process an action for a release direction action
func process_direction_action_released(event: InputEvent) -> void:
var dir = get_action_direction(event)
if dir == null:
return
reset_dir_move_time(dir)
func _input(event : InputEvent) -> void:
mouse_pos = viewport_container.get_local_mouse_position()
var viewport_size := viewport_container.rect_size
if event.is_action_pressed("middle_mouse") || event.is_action_pressed("space"):
drag = true
elif event.is_action_released("middle_mouse") || event.is_action_released("space"):
drag = false
if Global.can_draw && Rect2(Vector2.ZERO, viewport_size).has_point(mouse_pos):
if event.is_action_pressed("zoom_in"): # Wheel Up Event
zoom_camera(-1)
elif event.is_action_pressed("zoom_out"): # Wheel Down Event
zoom_camera(1)
elif event is InputEventMouseMotion && drag:
offset = offset - event.relative * zoom
elif is_action_direction_pressed(event):
process_direction_action_pressed(event)
elif is_action_direction_released(event):
process_direction_action_released(event)
Global.horizontal_ruler.update()
Global.vertical_ruler.update()
# Zoom Camera
func zoom_camera(dir : int) -> void:
var viewport_size := viewport_container.rect_size
if Global.smooth_zoom:
var zoom_margin = zoom * dir / 5
var new_zoom = zoom + zoom_margin
if new_zoom > zoom_min && new_zoom < zoom_max:
var new_offset = offset + (-0.5 * viewport_size + mouse_pos) * (zoom - new_zoom)
tween.interpolate_property(self, "zoom", zoom, new_zoom, 0.05, Tween.TRANS_LINEAR, Tween.EASE_IN)
tween.interpolate_property(self, "offset", offset, new_offset, 0.05, Tween.TRANS_LINEAR, Tween.EASE_IN)
tween.start()
if name == "Camera2D":
Global.zoom_level_label.text = str(round(100 / new_zoom.x)) + " %"
else:
var prev_zoom := zoom
var zoom_margin = zoom * dir / 10
if zoom + zoom_margin > zoom_min:
zoom += zoom_margin
if zoom > zoom_max:
zoom = zoom_max
offset = offset + (-0.5 * viewport_size + mouse_pos) * (prev_zoom - zoom)
if name == "Camera2D":
Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %"
func _on_tween_step(_object: Object, _key: NodePath, _elapsed: float, _value: Object) -> void:
Global.horizontal_ruler.update()
Global.vertical_ruler.update()

1104
src/Canvas.gd Normal file

File diff suppressed because it is too large Load diff

7
src/Canvas.tscn Normal file
View file

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Canvas.gd" type="Script" id=1]
[node name="Canvas" type="Node2D"]
script = ExtResource( 1 )

121
src/CelButton.gd Normal file
View file

@ -0,0 +1,121 @@
extends Button
var frame := 0
var layer := 0
onready var popup_menu : PopupMenu = $PopupMenu
func _ready() -> void:
hint_tooltip = "Frame: %s, Layer: %s" % [frame + 1, layer]
if Global.canvases[frame] in Global.layers[layer][5]:
get_node("LinkedIndicator").visible = true
popup_menu.set_item_text(4, "Unlink Cel")
popup_menu.set_item_metadata(4, "Unlink Cel")
else:
get_node("LinkedIndicator").visible = false
popup_menu.set_item_text(4, "Link Cel")
popup_menu.set_item_metadata(4, "Link Cel")
func _on_CelButton_pressed() -> void:
if Input.is_action_just_released("left_mouse"):
Global.current_frame = frame
Global.current_layer = layer
elif Input.is_action_just_released("right_mouse"):
if Global.canvases.size() == 1:
popup_menu.set_item_disabled(0, true)
popup_menu.set_item_disabled(2, true)
popup_menu.set_item_disabled(3, true)
else:
popup_menu.set_item_disabled(0, false)
if frame > 0:
popup_menu.set_item_disabled(2, false)
if frame < Global.canvases.size() - 1:
popup_menu.set_item_disabled(3, false)
popup_menu.popup(Rect2(get_global_mouse_position(), Vector2.ONE))
pressed = !pressed
elif Input.is_action_just_released("middle_mouse"): # Middle mouse click
pressed = !pressed
Global.animation_timeline._on_DeleteFrame_pressed(frame)
else: # An example of this would be Space
pressed = !pressed
func _on_PopupMenu_id_pressed(ID : int) -> void:
match ID:
0: # Remove Frame
Global.animation_timeline._on_DeleteFrame_pressed(frame)
1: # Clone Frame
Global.animation_timeline._on_CopyFrame_pressed(frame)
2: # Move Left
change_frame_order(-1)
3: # Move Right
change_frame_order(1)
4: # Unlink Cel
var cel_index : int = Global.layers[layer][5].find(Global.canvases[frame])
var c = Global.canvases[frame]
var new_layers := Global.layers.duplicate(true)
var new_canvas_layers : Array = c.layers.duplicate(true)
if popup_menu.get_item_metadata(4) == "Unlink Cel":
new_layers[layer][5].remove(cel_index)
var sprite := Image.new()
sprite.copy_from(Global.canvases[frame].layers[layer][0])
sprite.lock()
var tex := ImageTexture.new()
tex.create_from_image(sprite, 0)
new_canvas_layers[layer][0] = sprite
new_canvas_layers[layer][1] = tex
Global.undo_redo.create_action("Unlink Cel")
Global.undo_redo.add_do_property(Global, "layers", new_layers)
Global.undo_redo.add_do_property(c, "layers", new_canvas_layers)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_undo_property(c, "layers", c.layers)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvases[frame]], layer)
Global.undo_redo.add_do_method(Global, "redo", [Global.canvases[frame]], layer)
Global.undo_redo.commit_action()
elif popup_menu.get_item_metadata(4) == "Link Cel":
new_layers[layer][5].append(Global.canvases[frame])
Global.undo_redo.create_action("Link Cel")
Global.undo_redo.add_do_property(Global, "layers", new_layers)
if new_layers[layer][5].size() > 1:
# If there are already linked cels, set the current cel's image
# to the first linked cel's image
new_canvas_layers[layer][0] = new_layers[layer][5][0].layers[layer][0]
new_canvas_layers[layer][1] = new_layers[layer][5][0].layers[layer][1]
Global.undo_redo.add_do_property(c, "layers", new_canvas_layers)
Global.undo_redo.add_undo_property(c, "layers", c.layers)
Global.undo_redo.add_undo_property(Global, "layers", Global.layers)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvases[frame]], layer)
Global.undo_redo.add_do_method(Global, "redo", [Global.canvases[frame]], layer)
Global.undo_redo.commit_action()
func change_frame_order(rate : int) -> void:
var change = frame + rate
var new_canvases := Global.canvases.duplicate()
var temp = new_canvases[frame]
new_canvases[frame] = new_canvases[change]
new_canvases[change] = temp
Global.undo_redo.create_action("Change Frame Order")
Global.undo_redo.add_do_property(Global, "canvases", new_canvases)
Global.undo_redo.add_do_property(Global.canvases[frame], "frame", change)
Global.undo_redo.add_do_property(Global.canvases[change], "frame", frame)
if Global.current_frame == frame:
Global.undo_redo.add_do_property(Global, "current_frame", change)
Global.undo_redo.add_undo_property(Global, "current_frame", Global.current_frame)
Global.undo_redo.add_undo_property(Global, "canvases", Global.canvases)
Global.undo_redo.add_undo_property(Global.canvases[frame], "frame", frame)
Global.undo_redo.add_undo_property(Global.canvases[change], "frame", change)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvases[frame]])
Global.undo_redo.add_do_method(Global, "redo", [Global.canvases[frame]])
Global.undo_redo.commit_action()

47
src/CelButton.tscn Normal file
View file

@ -0,0 +1,47 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/CelButton.gd" type="Script" id=1]
[node name="CelButton" type="Button"]
margin_top = 18.0
margin_right = 36.0
margin_bottom = 54.0
rect_min_size = Vector2( 36, 36 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
toggle_mode = true
button_mask = 7
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CelTexture" type="TextureRect" parent="."]
margin_left = 2.0
margin_top = 1.78536
margin_right = 34.0
margin_bottom = 33.7854
rect_min_size = Vector2( 32, 32 )
size_flags_horizontal = 0
size_flags_vertical = 0
expand = true
stretch_mode = 6
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PopupMenu" type="PopupMenu" parent="."]
margin_right = 20.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
items = [ "Remove Frame", null, 0, false, true, -1, 0, null, "", false, "Clone Frame", null, 0, false, false, -1, 0, null, "", false, "Move Left", null, 0, false, true, -1, 0, null, "", false, "Move Right", null, 0, false, true, -1, 0, null, "", false, "Link Cel", null, 0, false, false, -1, 0, null, "", false ]
[node name="LinkedIndicator" type="Polygon2D" parent="."]
visible = false
color = Color( 0.0627451, 0.741176, 0.215686, 1 )
invert_enable = true
invert_border = 1.0
polygon = PoolVector2Array( 0, 0, 36, 0, 36, 36, 0, 36 )
[connection signal="pressed" from="." to="." method="_on_CelButton_pressed"]
[connection signal="id_pressed" from="PopupMenu" to="." method="_on_PopupMenu_id_pressed"]

122
src/Dialogs/AboutDialog.gd Normal file
View file

@ -0,0 +1,122 @@
extends WindowDialog
onready var credits = $AboutUI/Credits
onready var groups : Tree = $AboutUI/Credits/Groups
onready var developer_container = $AboutUI/Credits/Developers
onready var contributors_container = $AboutUI/Credits/Contributors
onready var donors_container = $AboutUI/Credits/Donors
onready var translators_container = $AboutUI/Credits/Translators
onready var developers : Tree = $AboutUI/Credits/Developers/DeveloperTree
onready var contributors : Tree = $AboutUI/Credits/Contributors/ContributorTree
onready var donors : Tree = $AboutUI/Credits/Donors/DonorTree
onready var translators : Tree = $AboutUI/Credits/Translators/TranslatorTree
func _ready() -> void:
var contributor_root := contributors.create_item()
contributors.create_item(contributor_root).set_text(0, " Hugo Locurcio (Calinou)")
contributors.create_item(contributor_root).set_text(0, " CheetoHead (greusser)")
contributors.create_item(contributor_root).set_text(0, " Michael Alexsander (YeldhamDev)")
contributors.create_item(contributor_root).set_text(0, " Schweini07")
contributors.create_item(contributor_root).set_text(0, " Martin Zabinski")
contributors.create_item(contributor_root).set_text(0, " azagaya")
contributors.create_item(contributor_root).set_text(0, " Andreev Andrei")
contributors.create_item(contributor_root).set_text(0, " Martin Novák (novhack)")
contributors.create_item(contributor_root).set_text(0, " Marco Galli (Gaarco)")
contributors.create_item(contributor_root).set_text(0, " Subhang Nanduri (SbNanduri)")
contributors.create_item(contributor_root).set_text(0, " danielnaoexiste")
contributors.create_item(contributor_root).set_text(0, " Noah Burck (haonkrub)")
contributors.create_item(contributor_root).set_text(0, " Darshan Phaldesai (luiq54)")
contributors.create_item(contributor_root).set_text(0, " Matheus Pesegoginski (MatheusPese)")
contributors.create_item(contributor_root).set_text(0, " sapient_cogbag")
contributors.create_item(contributor_root).set_text(0, " Kinwailo")
contributors.create_item(contributor_root).set_text(0, " Igor Santarek (jegor377)")
contributors.create_item(contributor_root).set_text(0, " Dávid Gábor BODOR (dragonfi)")
var donors_root := donors.create_item()
donors.create_item(donors_root).set_text(0, " pcmxms")
donors.create_item(donors_root).set_text(0, " Mike King")
func _on_AboutDialog_about_to_show() -> void:
var current_version : String = ProjectSettings.get_setting("application/config/Version")
window_title = tr("About Pixelorama") + " " + current_version
var groups_root := groups.create_item()
var developers_button := groups.create_item(groups_root)
var contributors_button := groups.create_item(groups_root)
var donors_button := groups.create_item(groups_root)
var translators_button := groups.create_item(groups_root)
developers_button.set_text(0, " " + tr("Developers"))
# We use metadata to avoid being affected by translations
developers_button.set_metadata(0, "Developers")
developers_button.select(0)
contributors_button.set_text(0, " " + tr("Contributors"))
contributors_button.set_metadata(0, "Contributors")
donors_button.set_text(0, " " + tr("Donors"))
donors_button.set_metadata(0, "Donors")
translators_button.set_text(0, " " + tr("Translators"))
translators_button.set_metadata(0, "Translators")
var dev_root := developers.create_item()
developers.create_item(dev_root).set_text(0, " Manolis Papadeas (Overloaded) - " + tr("Lead Programmer"))
developers.create_item(dev_root).set_text(0, " John Nikitakis (Erevos) - " + tr("UI Designer"))
# Translators
var translators_root := translators.create_item()
translators.create_item(translators_root).set_text(0, " Manolis Papadeas (Overloaded) - " + tr("Greek"))
translators.create_item(translators_root).set_text(0, " Xenofon Konitsas (huskee) - " + tr("Greek"))
translators.create_item(translators_root).set_text(0, " Hugo Locurcio (Calinou) - " + tr("French"))
translators.create_item(translators_root).set_text(0, " blackjoker77777 - " + tr("French"))
translators.create_item(translators_root).set_text(0, " Schweini07 - " + tr("German"))
translators.create_item(translators_root).set_text(0, " Martin Zabinski (Martin1991zab) - " + tr("German"))
translators.create_item(translators_root).set_text(0, " Dawid Niedźwiedzki (tiritto) - " + tr("Polish"))
translators.create_item(translators_root).set_text(0, " Serhiy Dmytryshyn (dies) - " + tr("Polish"))
translators.create_item(translators_root).set_text(0, " Michael Alexsander (YeldhamDev) - " + tr("Brazilian Portuguese"))
translators.create_item(translators_root).set_text(0, " Cedulio Cezar (ceduliocezar) - " + tr("Brazilian Portuguese"))
translators.create_item(translators_root).set_text(0, " Andreev Andrei - " + tr("Russian"))
translators.create_item(translators_root).set_text(0, " ax trifonov (ax34) - " + tr("Russian"))
translators.create_item(translators_root).set_text(0, " Artem (blinovartem) - " + tr("Russian"))
translators.create_item(translators_root).set_text(0, " JunYouIntrovert - " + tr("Chinese Traditional"))
translators.create_item(translators_root).set_text(0, " Chenxu Wang - " + tr("Chinese Simplified"))
translators.create_item(translators_root).set_text(0, " Marco Galli (Gaarco) - " + tr("Italian"))
translators.create_item(translators_root).set_text(0, " StarFang208 - " + tr("Italian"))
translators.create_item(translators_root).set_text(0, " azagaya - " + tr("Spanish"))
translators.create_item(translators_root).set_text(0, " Lilly And (KatieAnd) - " + tr("Spanish"))
translators.create_item(translators_root).set_text(0, " Agnis Aldiņš (NeZvers) - " + tr("Latvian"))
translators.create_item(translators_root).set_text(0, " Teashrock - " + tr("Esperanto"))
func _on_AboutDialog_popup_hide() -> void:
groups.clear()
developers.clear()
func _on_Groups_item_selected() -> void:
for child in credits.get_children():
if child != groups:
child.visible = false
var selected : String = groups.get_selected().get_metadata(0)
if "Developers" in selected:
developer_container.visible = true
elif "Contributors" in selected:
contributors_container.visible = true
elif "Donors" in selected:
donors_container.visible = true
elif "Translators" in selected:
translators_container.visible = true
func _on_Website_pressed() -> void:
OS.shell_open("https://www.orama-interactive.com/pixelorama")
func _on_GitHub_pressed() -> void:
OS.shell_open("https://github.com/Orama-Interactive/Pixelorama")
func _on_Donate_pressed() -> void:
OS.shell_open("https://www.patreon.com/OramaInteractive")

View file

@ -0,0 +1,208 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://src/Dialogs/AboutDialog.gd" type="Script" id=1]
[ext_resource path="res://Assets/Graphics/icon_64x64.png" type="Texture" id=2]
[ext_resource path="res://Assets/Fonts/Roboto-Italic.tres" type="DynamicFont" id=3]
[ext_resource path="res://Assets/Graphics/orama_64x64.png" type="Texture" id=4]
[ext_resource path="res://Assets/Fonts/Roboto-Small.tres" type="DynamicFont" id=5]
[node name="AboutDialog" type="WindowDialog"]
margin_right = 512.0
margin_bottom = 288.0
rect_min_size = Vector2( 512, 288 )
window_title = "About Pixelorama"
resizable = true
script = ExtResource( 1 )
[node name="AboutUI" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 12.0
margin_top = 4.0
margin_right = -12.0
margin_bottom = -8.0
alignment = 1
[node name="IconsButtons" type="HBoxContainer" parent="AboutUI"]
margin_right = 488.0
margin_bottom = 64.0
[node name="PixeloramaLogo" type="TextureRect" parent="AboutUI/IconsButtons"]
margin_right = 64.0
margin_bottom = 64.0
texture = ExtResource( 2 )
[node name="SloganAndLinks" type="CenterContainer" parent="AboutUI/IconsButtons"]
margin_left = 68.0
margin_right = 420.0
margin_bottom = 64.0
size_flags_horizontal = 3
[node name="VBoxContainer" type="VBoxContainer" parent="AboutUI/IconsButtons/SloganAndLinks"]
margin_left = 64.0
margin_top = 12.0
margin_right = 288.0
margin_bottom = 51.0
[node name="Pixelorama" type="Label" parent="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer"]
margin_right = 224.0
margin_bottom = 15.0
custom_fonts/font = ExtResource( 3 )
text = "Pixelorama - Pixelate your dreams!"
align = 1
[node name="LinkButtons" type="HBoxContainer" parent="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer"]
margin_top = 19.0
margin_right = 224.0
margin_bottom = 39.0
[node name="Website" type="Button" parent="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer/LinkButtons"]
margin_right = 65.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "Website"
[node name="GitHub" type="Button" parent="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer/LinkButtons"]
margin_left = 69.0
margin_right = 162.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "GitHub Repo"
[node name="Donate" type="Button" parent="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer/LinkButtons"]
margin_left = 166.0
margin_right = 224.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
text = "Donate"
[node name="OramaLogo" type="TextureRect" parent="AboutUI/IconsButtons"]
margin_left = 424.0
margin_right = 488.0
margin_bottom = 64.0
texture = ExtResource( 4 )
[node name="HSeparator" type="HSeparator" parent="AboutUI"]
margin_top = 68.0
margin_right = 488.0
margin_bottom = 72.0
[node name="Credits" type="HSplitContainer" parent="AboutUI"]
margin_top = 76.0
margin_right = 488.0
margin_bottom = 233.0
size_flags_vertical = 3
[node name="Groups" type="Tree" parent="AboutUI/Credits"]
margin_right = 120.0
margin_bottom = 157.0
rect_min_size = Vector2( 120, 120 )
custom_constants/item_margin = -2
hide_root = true
[node name="Developers" type="VBoxContainer" parent="AboutUI/Credits"]
margin_left = 132.0
margin_right = 488.0
margin_bottom = 157.0
size_flags_horizontal = 3
[node name="Label" type="Label" parent="AboutUI/Credits/Developers"]
margin_right = 356.0
margin_bottom = 14.0
text = "Development Team"
[node name="DeveloperTree" type="Tree" parent="AboutUI/Credits/Developers"]
margin_top = 18.0
margin_right = 356.0
margin_bottom = 157.0
size_flags_vertical = 3
custom_constants/item_margin = -2
custom_constants/button_margin = 2
hide_root = true
[node name="Contributors" type="VBoxContainer" parent="AboutUI/Credits"]
visible = false
margin_left = 254.0
margin_right = 496.0
margin_bottom = 126.0
size_flags_horizontal = 3
[node name="Label" type="Label" parent="AboutUI/Credits/Contributors"]
margin_right = 242.0
margin_bottom = 14.0
text = "GitHub Contributors"
[node name="ContributorTree" type="Tree" parent="AboutUI/Credits/Contributors"]
margin_top = 18.0
margin_right = 242.0
margin_bottom = 126.0
size_flags_vertical = 3
custom_constants/item_margin = -2
hide_root = true
[node name="Donors" type="VBoxContainer" parent="AboutUI/Credits"]
visible = false
margin_left = 254.0
margin_right = 496.0
margin_bottom = 126.0
size_flags_horizontal = 3
[node name="Label" type="Label" parent="AboutUI/Credits/Donors"]
margin_right = 242.0
margin_bottom = 14.0
text = "Donors"
[node name="DonorTree" type="Tree" parent="AboutUI/Credits/Donors"]
margin_top = 18.0
margin_right = 242.0
margin_bottom = 126.0
size_flags_vertical = 3
custom_constants/item_margin = -2
hide_root = true
[node name="Translators" type="VBoxContainer" parent="AboutUI/Credits"]
visible = false
margin_left = 254.0
margin_right = 496.0
margin_bottom = 126.0
size_flags_horizontal = 3
[node name="Label" type="Label" parent="AboutUI/Credits/Translators"]
margin_right = 242.0
margin_bottom = 14.0
text = "Translators"
[node name="TranslatorTree" type="Tree" parent="AboutUI/Credits/Translators"]
margin_top = 18.0
margin_right = 242.0
margin_bottom = 126.0
size_flags_vertical = 3
custom_constants/item_margin = -2
hide_root = true
[node name="HSeparator2" type="HSeparator" parent="AboutUI"]
margin_top = 237.0
margin_right = 488.0
margin_bottom = 241.0
[node name="MadeBy" type="Label" parent="AboutUI"]
margin_top = 245.0
margin_right = 488.0
margin_bottom = 259.0
text = "Developed by Orama Interactive"
align = 1
[node name="Copyright" type="Label" parent="AboutUI"]
margin_top = 263.0
margin_right = 488.0
margin_bottom = 276.0
custom_fonts/font = ExtResource( 5 )
text = "Copyright 2019-2020 Orama Interactive"
align = 1
[connection signal="about_to_show" from="." to="." method="_on_AboutDialog_about_to_show"]
[connection signal="popup_hide" from="." to="." method="_on_AboutDialog_popup_hide"]
[connection signal="pressed" from="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer/LinkButtons/Website" to="." method="_on_Website_pressed"]
[connection signal="pressed" from="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer/LinkButtons/GitHub" to="." method="_on_GitHub_pressed"]
[connection signal="pressed" from="AboutUI/IconsButtons/SloganAndLinks/VBoxContainer/LinkButtons/Donate" to="." method="_on_Donate_pressed"]
[connection signal="item_selected" from="AboutUI/Credits/Groups" to="." method="_on_Groups_item_selected"]

View file

@ -0,0 +1,133 @@
extends ConfirmationDialog
onready var templates_options = $VBoxContainer/OptionsContainer/TemplatesOptions
onready var ratio_box = $VBoxContainer/OptionsContainer/RatioCheckBox
onready var width_value = $VBoxContainer/OptionsContainer/WidthValue
onready var height_value = $VBoxContainer/OptionsContainer/HeightValue
onready var fill_color_node = $VBoxContainer/OptionsContainer/FillColor
onready var size_value = Vector2()
# Template Id identifier
enum Templates {
TDefault = 0,
T16 = 1,
T32 = 2,
T64 = 3,
T128 = 4,
GB = 5,
GBA = 6,
NES_NTSC = 7,
NES_PAL = 8,
SNES_NTSC = 9,
SNES_PAL = 10
}
# Template actual value, without Default because we get it from Global
var TResolutions = {
Templates.T16: Vector2(16,16),
Templates.T32: Vector2(32,32),
Templates.T64: Vector2(64,64),
Templates.T128: Vector2(128,128),
Templates.GB: Vector2(160,144),
Templates.GBA: Vector2(240,160),
Templates.NES_NTSC: Vector2(256,224),
Templates.NES_PAL: Vector2(256,240),
Templates.SNES_NTSC: Vector2(512,448),
Templates.SNES_PAL: Vector2(512,480),
}
var TStrings ={
Templates.T16: "",
Templates.T32: "",
Templates.T64: "",
Templates.T128: "",
Templates.GB: "GB",
Templates.GBA: "GBA",
Templates.NES_NTSC: "NES (NTSC)",
Templates.NES_PAL: "NES (PAL)",
Templates.SNES_NTSC: "SNES (NTSC)",
Templates.SNES_PAL: "SNES (PAL)"
}
func _ready() -> void:
ratio_box.connect("pressed", self, "_on_RatioCheckBox_toggled", [ratio_box.pressed])
templates_options.connect("item_selected", self, "_on_TemplatesOptions_item_selected")
_CreateOptionList()
func _CreateOptionList() -> void:
for i in Templates.values():
if i > 0:
if TStrings[i] != "":
templates_options.add_item("{width}x{height} - {name}".format({"width":TResolutions[i].x, "height":TResolutions[i].y, "name":TStrings[i]}), i)
else:
templates_options.add_item("{width}x{height}".format({"width":TResolutions[i].x, "height":TResolutions[i].y}), i)
func _on_CreateNewImage_confirmed() -> void:
var width : int = width_value.value
var height : int = height_value.value
var fill_color : Color = fill_color_node.color
Global.clear_canvases()
Global.layers.clear()
# Store [Layer name (0), Layer visibility boolean (1), Layer lock boolean (2), Frame container (3),
# will new frames be linked boolean (4), Array of linked frames (5)]
Global.layers.append([tr("Layer") + " 0", true, false, HBoxContainer.new(), false, []])
Global.canvas = load("res://src/Canvas.tscn").instance()
Global.canvas.size = Vector2(width, height).floor()
Global.canvases.append(Global.canvas)
Global.canvas_parent.add_child(Global.canvas)
Global.current_layer = 0
Global.canvases = Global.canvases # To trigger Global.canvases_changed()
Global.current_frame = 0
Global.layers = Global.layers # To trigger Global.layers_changed()
Global.project_has_changed = false
if fill_color.a > 0:
Global.canvas.layers[0][0].fill(fill_color)
Global.canvas.layers[0][0].lock()
Global.canvas.update_texture(0)
func _on_CreateNewImage_about_to_show() -> void:
width_value.value = Global.default_image_width
height_value.value = Global.default_image_height
fill_color_node.color = Global.default_fill_color
templates_options.selected = Templates.TDefault
ratio_box.pressed = false
for spin_box in [width_value, height_value]:
if spin_box.is_connected("value_changed", self, "_on_SizeValue_value_changed"):
spin_box.disconnect("value_changed", self, "_on_SizeValue_value_changed")
var aspect_ratio: float
func _on_RatioCheckBox_toggled(_button_pressed: bool) -> void:
aspect_ratio = width_value.value / height_value.value
for spin_box in [width_value, height_value]:
if spin_box.is_connected("value_changed", self, "_on_SizeValue_value_changed"):
spin_box.disconnect("value_changed", self, "_on_SizeValue_value_changed")
else:
spin_box.connect("value_changed", self, "_on_SizeValue_value_changed")
func _on_SizeValue_value_changed(value: float) -> void:
if width_value.value == value:
height_value.value = width_value.value / aspect_ratio
if height_value.value == value:
width_value.value = height_value.value * aspect_ratio
func _on_TemplatesOptions_item_selected(id: int) -> void:
if id != Templates.TDefault:
size_value = TResolutions[id]
else:
width_value.value = Global.default_image_width
height_value.value = Global.default_image_height
width_value.value = size_value.x
height_value.value = size_value.y

View file

@ -0,0 +1,123 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Dialogs/CreateNewImage.gd" type="Script" id=1]
[node name="CreateNewImage" type="ConfirmationDialog"]
margin_right = 300.0
margin_bottom = 200.0
rect_min_size = Vector2( 375, 200 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 367.0
margin_bottom = 164.0
size_flags_horizontal = 0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ImageSize" type="Label" parent="VBoxContainer"]
margin_right = 359.0
margin_bottom = 14.0
text = "Image Size"
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
margin_top = 18.0
margin_right = 359.0
margin_bottom = 22.0
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"]
margin_top = 26.0
margin_right = 359.0
margin_bottom = 154.0
custom_constants/vseparation = 4
custom_constants/hseparation = 2
columns = 2
[node name="TemplatesLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 3.0
margin_right = 112.0
margin_bottom = 17.0
text = "Templates:"
[node name="TemplatesOptions" type="OptionButton" parent="VBoxContainer/OptionsContainer"]
margin_left = 114.0
margin_right = 189.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
toggle_mode = false
text = "Default"
items = [ "Default", null, false, 0, null ]
selected = 0
[node name="RatioLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 29.0
margin_right = 112.0
margin_bottom = 43.0
text = "Lock aspect ratio:"
[node name="RatioCheckBox" type="CheckBox" parent="VBoxContainer/OptionsContainer"]
margin_left = 114.0
margin_top = 24.0
margin_right = 189.0
margin_bottom = 48.0
mouse_default_cursor_shape = 2
__meta__ = {
"_edit_use_anchors_": false
}
[node name="WidthLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 57.0
margin_right = 112.0
margin_bottom = 71.0
text = "Width:"
[node name="WidthValue" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
margin_left = 114.0
margin_top = 52.0
margin_right = 189.0
margin_bottom = 76.0
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 64.0
suffix = "px"
[node name="Height" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 85.0
margin_right = 112.0
margin_bottom = 99.0
text = "Height:"
[node name="HeightValue" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
margin_left = 114.0
margin_top = 80.0
margin_right = 189.0
margin_bottom = 104.0
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 64.0
suffix = "px"
[node name="FillColorLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 111.0
margin_right = 112.0
margin_bottom = 125.0
text = "Fill with color:"
[node name="FillColor" type="ColorPickerButton" parent="VBoxContainer/OptionsContainer"]
margin_left = 114.0
margin_top = 108.0
margin_right = 189.0
margin_bottom = 128.0
rect_min_size = Vector2( 64, 20 )
color = Color( 0, 0, 0, 0 )
[connection signal="about_to_show" from="." to="." method="_on_CreateNewImage_about_to_show"]
[connection signal="confirmed" from="." to="." method="_on_CreateNewImage_confirmed"]

629
src/Dialogs/ExportDialog.gd Normal file
View file

@ -0,0 +1,629 @@
extends AcceptDialog
enum ExportTab { FRAME = 0, SPRITESHEET = 1, ANIMATION = 2 }
var current_tab : int = ExportTab.FRAME
# All canvases and their layers processed/blended into images
var processed_images = [] # Image[]
# Frame options
var frame_number := 0
# Spritesheet options
enum Orientation { ROWS = 0, COLUMNS = 1 }
var orientation : int = Orientation.ROWS
# How many rows/columns before new line is added
var lines_count := 1
# Animation options
enum AnimationType { MULTIPLE_FILES = 0, ANIMATED = 1 }
var animation_type : int = AnimationType.MULTIPLE_FILES
var background_color : Color = Color.white
enum AnimationDirection { FORWARD = 0, BACKWARDS = 1, PING_PONG = 2 }
var direction : int = AnimationDirection.FORWARD
# Options
var resize := 100
var interpolation := 0 # Image.Interpolation
var new_dir_for_each_frame_tag : bool = true # you don't need to store this after export
# Export directory path and export file name
var directory_path := ""
var file_name := ""
var file_format : int = FileFormat.PNG
enum FileFormat { PNG = 0, GIF = 1}
var file_exists_alert = "File %s already exists. Overwrite?"
# Store all settings after export, enables a quick re-export with same settings
var was_exported : bool = false
var exported_tab : int
var exported_frame_number : int
var exported_orientation : int
var exported_lines_count : int
var exported_animation_type : int
var exported_background_color : Color
var exported_direction : int
var exported_resize : int
var exported_interpolation : int
var exported_directory_path : String
var exported_file_name : String
var exported_file_format : int
# Export coroutine signal
signal resume_export_function()
var stop_export = false
var animated_preview_current_frame := 0
var animated_preview_frames = []
func _ready() -> void:
$VBoxContainer/Tabs.add_tab("Frame")
$VBoxContainer/Tabs.add_tab("Spritesheet")
$VBoxContainer/Tabs.add_tab("Animation")
if OS.get_name() == "Windows":
add_button("Cancel", true, "cancel")
$Popups/FileExistsAlert.add_button("Cancel Export", true, "cancel")
else:
add_button("Cancel", false, "cancel")
$Popups/FileExistsAlert.add_button("Cancel Export", false, "cancel")
# Disable GIF export for unsupported platforms
if not $GifExporter.is_platform_supported():
$VBoxContainer/AnimationOptions/AnimationType.selected = AnimationType.MULTIPLE_FILES
$VBoxContainer/AnimationOptions/AnimationType.disabled = true
func show_tab() -> void:
$VBoxContainer/FrameOptions.hide()
$VBoxContainer/SpritesheetOptions.hide()
$VBoxContainer/AnimationOptions.hide()
match current_tab:
ExportTab.FRAME:
file_format = FileFormat.PNG
$VBoxContainer/File/FileFormat.selected = FileFormat.PNG
$FrameTimer.stop()
if not was_exported:
frame_number = Global.current_frame + 1
$VBoxContainer/FrameOptions/FrameNumber/FrameNumber.max_value = Global.canvases.size() + 1
$VBoxContainer/FrameOptions/FrameNumber/FrameNumber.value = frame_number
process_frame()
$VBoxContainer/FrameOptions.show()
ExportTab.SPRITESHEET:
file_format = FileFormat.PNG
$VBoxContainer/File/FileFormat.selected = FileFormat.PNG
$FrameTimer.stop()
if not was_exported:
orientation = Orientation.ROWS
lines_count = int(ceil(sqrt(Global.canvases.size())))
$VBoxContainer/SpritesheetOptions/Orientation/Orientation.selected = orientation
$VBoxContainer/SpritesheetOptions/Orientation/LinesCount.max_value = Global.canvases.size()
$VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = lines_count
$VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Columns:"
process_spritesheet()
$VBoxContainer/SpritesheetOptions.show()
ExportTab.ANIMATION:
set_file_format_selector()
process_animation()
$VBoxContainer/AnimationOptions/AnimationType.selected = animation_type
$VBoxContainer/AnimationOptions/AnimatedOptions/BackgroundColor.color = background_color
$VBoxContainer/AnimationOptions/AnimatedOptions/Direction.selected = direction
$VBoxContainer/AnimationOptions.show()
set_preview()
$VBoxContainer/Tabs.current_tab = current_tab
func external_export() -> void:
restore_previous_export_settings()
match current_tab:
ExportTab.FRAME:
process_frame()
ExportTab.SPRITESHEET:
process_spritesheet()
ExportTab.ANIMATION:
process_animation()
export_processed_images(true)
func process_frame() -> void:
var canvas = Global.canvases[frame_number - 1]
var image := Image.new()
image.create(canvas.size.x, canvas.size.y, false, Image.FORMAT_RGBA8)
blend_layers(image, canvas)
processed_images.clear()
processed_images.append(image)
func process_spritesheet() -> void:
# If rows mode selected calculate columns count and vice versa
var spritesheet_columns = lines_count if orientation == Orientation.ROWS else frames_divided_by_spritesheet_lines()
var spritesheet_rows = lines_count if orientation == Orientation.COLUMNS else frames_divided_by_spritesheet_lines()
var width = Global.canvas.size.x * spritesheet_columns
var height = Global.canvas.size.y * spritesheet_rows
var whole_image := Image.new()
whole_image.create(width, height, false, Image.FORMAT_RGBA8)
whole_image.lock()
var origin := Vector2.ZERO
var hh := 0
var vv := 0
for canvas in Global.canvases:
if orientation == Orientation.ROWS:
if vv < spritesheet_columns:
origin.x = canvas.size.x * vv
vv += 1
else:
hh += 1
origin.x = 0
vv = 1
origin.y = canvas.size.y * hh
else:
if hh < spritesheet_rows:
origin.y = canvas.size.y * hh
hh += 1
else:
vv += 1
origin.y = 0
hh = 1
origin.x = canvas.size.x * vv
blend_layers(whole_image, canvas, origin)
processed_images.clear()
processed_images.append(whole_image)
func process_animation() -> void:
processed_images.clear()
for canvas in Global.canvases:
var image := Image.new()
image.create(canvas.size.x, canvas.size.y, false, Image.FORMAT_RGBA8)
blend_layers(image, canvas)
processed_images.append(image)
func set_preview() -> void:
remove_previews()
if processed_images.size() == 1 and current_tab != ExportTab.ANIMATION:
$VBoxContainer/PreviewScroll/Previews.columns = 1
add_image_preview(processed_images[0])
else:
match animation_type:
AnimationType.MULTIPLE_FILES:
$VBoxContainer/PreviewScroll/Previews.columns = ceil(sqrt(processed_images.size()))
for i in range(processed_images.size()):
add_image_preview(processed_images[i], i + 1)
AnimationType.ANIMATED:
$VBoxContainer/PreviewScroll/Previews.columns = 1
add_animated_preview()
func add_image_preview(image: Image, canvas_number: int = -1) -> void:
var container = create_preview_container()
var preview = create_preview_rect()
preview.texture = ImageTexture.new()
preview.texture.create_from_image(image, 0)
container.add_child(preview)
if canvas_number != -1:
var label = Label.new()
label.align = Label.ALIGN_CENTER
label.text = String(canvas_number)
container.add_child(label)
$VBoxContainer/PreviewScroll/Previews.add_child(container)
func add_animated_preview() -> void:
animated_preview_current_frame = processed_images.size() - 1 if direction == AnimationDirection.BACKWARDS else 0
animated_preview_frames = []
for processed_image in processed_images:
var texture = ImageTexture.new()
texture.create_from_image(processed_image, 0)
animated_preview_frames.push_back(texture)
var container = create_preview_container()
container.name = "PreviewContainer"
var preview = create_preview_rect()
preview.name = "Preview"
preview.texture = animated_preview_frames[animated_preview_current_frame]
container.add_child(preview)
$VBoxContainer/PreviewScroll/Previews.add_child(container)
$FrameTimer.start()
func create_preview_container() -> VBoxContainer:
var container = VBoxContainer.new()
container.size_flags_horizontal = SIZE_EXPAND_FILL
container.size_flags_vertical = SIZE_EXPAND_FILL
container.rect_min_size = Vector2(0, 128)
return container
func create_preview_rect() -> TextureRect:
var preview = TextureRect.new()
preview.expand = true
preview.size_flags_horizontal = SIZE_EXPAND_FILL
preview.size_flags_vertical = SIZE_EXPAND_FILL
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
return preview
func remove_previews() -> void:
for child in $VBoxContainer/PreviewScroll/Previews.get_children():
child.free()
func get_proccessed_image_animation_tag_and_start_id(processed_image_id : int) -> Array:
var result_animation_tag_and_start_id = null
for animation_tag in Global.animation_tags:
# Check if processed image is in frame tag and assign frame tag and start id if yes
# Then stop
if (processed_image_id + 1) >= animation_tag[2] and (processed_image_id + 1) <= animation_tag[3]:
result_animation_tag_and_start_id = [animation_tag[0], animation_tag[2]]
break
return result_animation_tag_and_start_id
func export_processed_images(ignore_overwrites : bool) -> void:
# Stop export if directory path or file name are not valid
var dir = Directory.new()
if not dir.dir_exists(directory_path) or not file_name.is_valid_filename():
$Popups/PathValidationAlert.popup_centered()
return
# Check export paths
var export_paths = []
for i in range(processed_images.size()):
stop_export = false
var multiple_files := true if (current_tab == ExportTab.ANIMATION && animation_type == AnimationType.MULTIPLE_FILES) else false
var export_path = create_export_path(multiple_files, i + 1)
# If user want to create new directory for each animation tag then check if directories exist and create them if not
if multiple_files and new_dir_for_each_frame_tag:
var frame_tag_directory := Directory.new()
if not frame_tag_directory.dir_exists(export_path.get_base_dir()):
frame_tag_directory.open(directory_path)
frame_tag_directory.make_dir(export_path.get_base_dir().get_file())
# Check if the file already exists
var fileCheck = File.new()
if fileCheck.file_exists(export_path):
# Ask user if he want's to overwrite the file
if not was_exported or (was_exported and not ignore_overwrites):
# Overwrite existing file?
$Popups/FileExistsAlert.dialog_text = file_exists_alert % export_path
$Popups/FileExistsAlert.popup_centered()
# Stops the function until the user decides if he want's to overwrite
yield(self, "resume_export_function")
if stop_export:
# User decided to stop export
return
export_paths.append(export_path)
# Only get one export path if single file animated image is exported
if current_tab == ExportTab.ANIMATION && animation_type == AnimationType.ANIMATED:
break
# Scale images that are to export
scale_processed_images()
if current_tab == ExportTab.ANIMATION && animation_type == AnimationType.ANIMATED:
var frame_delay_in_ms = Global.animation_timer.wait_time * 100
$GifExporter.begin_export(export_paths[0], processed_images[0].get_width(), processed_images[0].get_height(), frame_delay_in_ms, 0)
match direction:
AnimationDirection.FORWARD:
for i in range(processed_images.size()):
$GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms)
AnimationDirection.BACKWARDS:
for i in range(processed_images.size() - 1, -1, -1):
$GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms)
AnimationDirection.PING_PONG:
for i in range(0, processed_images.size()):
$GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms)
for i in range(processed_images.size() - 2, 0, -1):
$GifExporter.write_frame(processed_images[i], background_color, frame_delay_in_ms)
$GifExporter.end_export()
else:
for i in range(processed_images.size()):
var err = processed_images[i].save_png(export_paths[i])
if err != OK:
OS.alert("Can't save file")
# Store settings for quick export and when the dialog is opened again
was_exported = true
store_export_settings()
Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format)))
Global.notification_label("File(s) exported")
hide()
# Blends canvas layers into passed image starting from the origin position
func blend_layers(image: Image, canvas: Canvas, origin: Vector2 = Vector2(0, 0)) -> void:
image.lock()
var layer_i := 0
for layer in canvas.layers:
if Global.layers[layer_i][1]:
var layer_image := Image.new()
layer_image.copy_from(layer[0])
layer_image.lock()
if layer[2] < 1: # If we have layer transparency
for xx in layer_image.get_size().x:
for yy in layer_image.get_size().y:
var pixel_color := layer_image.get_pixel(xx, yy)
var alpha : float = pixel_color.a * layer[2]
layer_image.set_pixel(xx, yy, Color(pixel_color.r, pixel_color.g, pixel_color.b, alpha))
canvas.blend_rect(image, layer_image, Rect2(canvas.position, canvas.size), origin)
layer_i += 1
image.unlock()
func scale_processed_images() -> void:
for processed_image in processed_images:
if resize != 100:
processed_image.unlock()
processed_image.resize(processed_image.get_size().x * resize / 100, processed_image.get_size().y * resize / 100, interpolation)
func create_export_path(multifile: bool, frame: int = 0) -> String:
var path = file_name
# Only append frame number when there are multiple files exported
if multifile:
var frame_tag_and_start_id = get_proccessed_image_animation_tag_and_start_id(frame - 1)
# Check if exported frame is in frame tag
if frame_tag_and_start_id != null:
var frame_tag = frame_tag_and_start_id[0]
var start_id = frame_tag_and_start_id[1]
# Remove unallowed characters in frame tag directory
var regex := RegEx.new()
regex.compile("[^a-zA-Z0-9_]+")
var frame_tag_dir = regex.sub(frame_tag, "", true)
if new_dir_for_each_frame_tag:
# Add frame tag if frame has one
# (frame - start_id + 1) Makes frames id to start from 1 in each frame tag directory
path += "_" + frame_tag_dir + "_" + String(frame - start_id + 1)
return directory_path.plus_file(frame_tag_dir).plus_file(path + file_format_string(file_format))
else:
# Add frame tag if frame has one
# (frame - start_id + 1) Makes frames id to start from 1 in each frame tag
path += "_" + frame_tag_dir + "_" + String(frame - start_id + 1)
else:
path += "_" + String(frame)
return directory_path.plus_file(path + file_format_string(file_format))
func frames_divided_by_spritesheet_lines() -> int:
return int(ceil(Global.canvases.size() / float(lines_count)))
func file_format_string(format_enum : int) -> String:
match format_enum:
0: # PNG
return '.png'
1: # GIF
return '.gif'
_:
return ''
func set_file_format_selector() -> void:
$VBoxContainer/AnimationOptions/MultipleAnimationsDirectories.visible = false
match animation_type:
AnimationType.MULTIPLE_FILES:
file_format = FileFormat.PNG
$VBoxContainer/File/FileFormat.selected = FileFormat.PNG
$FrameTimer.stop()
$VBoxContainer/AnimationOptions/AnimatedOptions.hide()
$VBoxContainer/AnimationOptions/MultipleAnimationsDirectories.pressed = new_dir_for_each_frame_tag
$VBoxContainer/AnimationOptions/MultipleAnimationsDirectories.visible = true
AnimationType.ANIMATED:
file_format = FileFormat.GIF
$VBoxContainer/File/FileFormat.selected = FileFormat.GIF
$FrameTimer.wait_time = Global.animation_timer.wait_time
$VBoxContainer/AnimationOptions/AnimatedOptions.show()
func store_export_settings() -> void:
exported_tab = current_tab
exported_frame_number = frame_number
exported_orientation = orientation
exported_lines_count = lines_count
exported_animation_type = animation_type
exported_background_color = background_color
exported_direction = direction
exported_resize = resize
exported_interpolation = interpolation
exported_directory_path = directory_path
exported_file_name = file_name
exported_file_format = file_format
# Fill the dialog with previous export settings
func restore_previous_export_settings() -> void:
current_tab = exported_tab
frame_number = exported_frame_number if exported_frame_number <= Global.canvases.size() else Global.canvases.size()
orientation = exported_orientation
lines_count = exported_lines_count
animation_type = exported_animation_type
background_color = exported_background_color
direction = exported_direction
resize = exported_resize
interpolation = exported_interpolation
directory_path = exported_directory_path
file_name = exported_file_name
file_format = exported_file_format
func _on_ExportDialog_about_to_show() -> void:
# If export already occured - fill the dialog with previous export settings
if was_exported:
restore_previous_export_settings()
if directory_path.empty():
directory_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP)
# If export already occured - sets gui to show previous settings
$VBoxContainer/Options/Resize.value = resize
$VBoxContainer/Options/Interpolation.selected = interpolation
$VBoxContainer/Path/PathLineEdit.text = directory_path
$VBoxContainer/File/FileLineEdit.text = file_name
$VBoxContainer/File/FileFormat.selected = file_format
show_tab()
for child in $Popups.get_children(): # Set the theme for the popups
child.theme = Global.control.theme
file_exists_alert = tr("File %s already exists. Overwrite?") # Update translation
#$VBoxContainer/Tabs.set_tab_title(0, "Frame")
func _on_Tabs_tab_clicked(tab : int) -> void:
current_tab = tab
show_tab()
func _on_Frame_value_changed(value: float) -> void:
frame_number = value
process_frame()
set_preview()
func _on_Orientation_item_selected(id : int) -> void:
orientation = id
if orientation == Orientation.ROWS:
$VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Columns:"
else:
$VBoxContainer/SpritesheetOptions/Orientation/LinesCountLabel.text = "Rows:"
$VBoxContainer/SpritesheetOptions/Orientation/LinesCount.value = frames_divided_by_spritesheet_lines()
process_spritesheet()
set_preview()
func _on_LinesCount_value_changed(value : float) -> void:
lines_count = value
process_spritesheet()
set_preview()
func _on_AnimationType_item_selected(id : int) -> void:
animation_type = id
set_file_format_selector()
set_preview()
func _on_BackgroundColor_color_changed(color : Color) -> void:
background_color = color
func _on_Direction_item_selected(id : int) -> void:
direction = id
match id:
AnimationDirection.FORWARD:
animated_preview_current_frame = 0
AnimationDirection.BACKWARDS:
animated_preview_current_frame = processed_images.size() - 1
AnimationDirection.PING_PONG:
animated_preview_current_frame = 0
pingpong_direction = AnimationDirection.FORWARD
func _on_Resize_value_changed(value : float) -> void:
resize = value
func _on_Interpolation_item_selected(id: int) -> void:
interpolation = id
func _on_ExportDialog_confirmed() -> void:
export_processed_images(false)
func _on_ExportDialog_custom_action(action : String) -> void:
if action == "cancel":
hide()
func _on_PathButton_pressed() -> void:
$Popups/PathDialog.popup_centered()
func _on_PathLineEdit_text_changed(new_text : String) -> void:
directory_path = new_text
func _on_FileLineEdit_text_changed(new_text : String) -> void:
file_name = new_text
func _on_FileDialog_dir_selected(dir : String) -> void:
$VBoxContainer/Path/PathLineEdit.text = dir
directory_path = dir
func _on_FileFormat_item_selected(id : int) -> void:
file_format = id
func _on_FileExistsAlert_confirmed() -> void:
# Overwrite existing file
$Popups/FileExistsAlert.dialog_text = file_exists_alert
stop_export = false
emit_signal("resume_export_function")
func _on_FileExistsAlert_custom_action(action : String) -> void:
if action == "cancel":
# Cancel export
$Popups/FileExistsAlert.dialog_text = file_exists_alert
stop_export = true
emit_signal("resume_export_function")
$Popups/FileExistsAlert.hide()
var pingpong_direction = AnimationDirection.FORWARD
func _on_FrameTimer_timeout() -> void:
$VBoxContainer/PreviewScroll/Previews/PreviewContainer/Preview.texture = animated_preview_frames[animated_preview_current_frame]
match direction:
AnimationDirection.FORWARD:
if animated_preview_current_frame == animated_preview_frames.size() - 1:
animated_preview_current_frame = 0
else:
animated_preview_current_frame += 1
AnimationDirection.BACKWARDS:
if animated_preview_current_frame == 0:
animated_preview_current_frame = processed_images.size() - 1
else:
animated_preview_current_frame -= 1
AnimationDirection.PING_PONG:
match pingpong_direction:
AnimationDirection.FORWARD:
if animated_preview_current_frame == animated_preview_frames.size() - 1:
pingpong_direction = AnimationDirection.BACKWARDS
animated_preview_current_frame -= 1
if animated_preview_current_frame <= 0:
animated_preview_current_frame = 0
else:
animated_preview_current_frame += 1
AnimationDirection.BACKWARDS:
if animated_preview_current_frame == 0:
animated_preview_current_frame += 1
if animated_preview_current_frame >= animated_preview_frames.size() - 1:
animated_preview_current_frame = 0
pingpong_direction = AnimationDirection.FORWARD
else:
animated_preview_current_frame -= 1
func _on_ExportDialog_popup_hide() -> void:
$FrameTimer.stop()
func _on_MultipleAnimationsDirectories_toggled(button_pressed : bool) -> void:
new_dir_for_each_frame_tag = button_pressed

View file

@ -0,0 +1,389 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://src/Dialogs/ExportDialog.gd" type="Script" id=1]
[ext_resource path="res://addons/godot-gifexporter/src/GifExporter.gd" type="Script" id=2]
[node name="ExportDialog" type="AcceptDialog"]
margin_right = 532.0
margin_bottom = 530.0
rect_min_size = Vector2( 456, 530 )
window_title = "Export..."
resizable = true
dialog_hide_on_ok = false
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 524.0
margin_bottom = 494.0
rect_min_size = Vector2( 330, 0 )
size_flags_vertical = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Tabs" type="Tabs" parent="VBoxContainer"]
margin_right = 516.0
margin_bottom = 24.0
size_flags_vertical = 0
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
margin_top = 28.0
margin_right = 516.0
margin_bottom = 32.0
[node name="PreviewLabel" type="Label" parent="VBoxContainer"]
margin_top = 36.0
margin_right = 516.0
margin_bottom = 50.0
text = "Preview:"
[node name="PreviewScroll" type="ScrollContainer" parent="VBoxContainer"]
margin_top = 54.0
margin_right = 516.0
margin_bottom = 274.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Previews" type="GridContainer" parent="VBoxContainer/PreviewScroll"]
margin_right = 516.0
margin_bottom = 220.0
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 3
[node name="FrameOptions" type="VBoxContainer" parent="VBoxContainer"]
margin_top = 278.0
margin_right = 516.0
margin_bottom = 302.0
[node name="FrameNumber" type="HBoxContainer" parent="VBoxContainer/FrameOptions"]
margin_right = 516.0
margin_bottom = 24.0
[node name="FrameNumberLabel" type="Label" parent="VBoxContainer/FrameOptions/FrameNumber"]
margin_top = 5.0
margin_right = 44.0
margin_bottom = 19.0
text = "Frame:"
[node name="FrameNumber" type="SpinBox" parent="VBoxContainer/FrameOptions/FrameNumber"]
margin_left = 48.0
margin_right = 516.0
margin_bottom = 24.0
rect_min_size = Vector2( 100, 0 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
min_value = 1.0
page = 1.0
value = 1.0
rounded = true
align = 2
[node name="SpritesheetOptions" type="VBoxContainer" parent="VBoxContainer"]
margin_top = 306.0
margin_right = 516.0
margin_bottom = 330.0
[node name="Orientation" type="HBoxContainer" parent="VBoxContainer/SpritesheetOptions"]
margin_right = 516.0
margin_bottom = 24.0
alignment = 1
[node name="OrientationLabel" type="Label" parent="VBoxContainer/SpritesheetOptions/Orientation"]
margin_top = 5.0
margin_right = 77.0
margin_bottom = 19.0
text = "Orientation:"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Orientation" type="OptionButton" parent="VBoxContainer/SpritesheetOptions/Orientation"]
margin_left = 81.0
margin_right = 264.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Rows"
items = [ "Rows", null, false, 0, null, "Columns", null, false, 1, null ]
selected = 0
[node name="LinesCountLabel" type="Label" parent="VBoxContainer/SpritesheetOptions/Orientation"]
margin_left = 268.0
margin_top = 5.0
margin_right = 328.0
margin_bottom = 19.0
text = "Columns:"
[node name="LinesCount" type="SpinBox" parent="VBoxContainer/SpritesheetOptions/Orientation"]
margin_left = 332.0
margin_right = 516.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 1000.0
value = 1.0
align = 2
[node name="AnimationOptions" type="VBoxContainer" parent="VBoxContainer"]
margin_top = 334.0
margin_right = 516.0
margin_bottom = 386.0
[node name="AnimationType" type="OptionButton" parent="VBoxContainer/AnimationOptions"]
margin_right = 516.0
margin_bottom = 24.0
rect_min_size = Vector2( 0, 24 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "All frames as multiple files"
items = [ "All frames as multiple files", null, false, 0, null, "All frames as a single file animation", null, false, 1, null ]
selected = 0
[node name="MultipleAnimationsDirectories" type="CheckBox" parent="VBoxContainer/AnimationOptions"]
visible = false
margin_top = 28.0
margin_right = 516.0
margin_bottom = 52.0
hint_tooltip = "Creates multiple files but every file is stored in different directory that corresponds to its frame tag"
text = "Create new directory for each frame tag"
[node name="AnimatedOptions" type="HBoxContainer" parent="VBoxContainer/AnimationOptions"]
margin_top = 28.0
margin_right = 516.0
margin_bottom = 52.0
rect_min_size = Vector2( 0, 24 )
[node name="BackgroundColorLabel" type="Label" parent="VBoxContainer/AnimationOptions/AnimatedOptions"]
margin_top = 5.0
margin_right = 78.0
margin_bottom = 19.0
text = "Background:"
valign = 1
[node name="BackgroundColor" type="ColorPickerButton" parent="VBoxContainer/AnimationOptions/AnimatedOptions"]
margin_left = 82.0
margin_right = 263.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 7
color = Color( 1, 1, 1, 1 )
edit_alpha = false
[node name="DirectionLabel" type="Label" parent="VBoxContainer/AnimationOptions/AnimatedOptions"]
margin_left = 267.0
margin_top = 5.0
margin_right = 330.0
margin_bottom = 19.0
text = "Direction:"
[node name="Direction" type="OptionButton" parent="VBoxContainer/AnimationOptions/AnimatedOptions"]
margin_left = 334.0
margin_right = 516.0
margin_bottom = 24.0
rect_min_size = Vector2( 100, 0 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Forward"
items = [ "Forward", null, false, 0, null, "Backwards", null, false, 1, null, "Ping-Pong", null, false, 2, null ]
selected = 0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HSeparator2" type="HSeparator" parent="VBoxContainer"]
margin_top = 390.0
margin_right = 516.0
margin_bottom = 394.0
[node name="Options" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 398.0
margin_right = 516.0
margin_bottom = 422.0
[node name="ResizeLabel" type="Label" parent="VBoxContainer/Options"]
margin_top = 5.0
margin_right = 46.0
margin_bottom = 19.0
rect_min_size = Vector2( 30, 0 )
text = "Resize:"
align = 2
[node name="Resize" type="SpinBox" parent="VBoxContainer/Options"]
margin_left = 50.0
margin_right = 235.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
min_value = 10.0
max_value = 1000.0
step = 100.0
value = 100.0
align = 2
suffix = "%"
[node name="InterpolationLabel" type="Label" parent="VBoxContainer/Options"]
margin_left = 239.0
margin_top = 5.0
margin_right = 326.0
margin_bottom = 19.0
rect_min_size = Vector2( 30, 0 )
text = "Interpolation:"
align = 2
[node name="Interpolation" type="OptionButton" parent="VBoxContainer/Options"]
margin_left = 330.0
margin_right = 516.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Nearest"
align = 2
items = [ "Nearest", null, false, 0, null, "Bilinear", null, false, 1, null, "Cubic", null, false, 2, null, "Trilinear", null, false, 3, null, "Lanczos", null, false, 4, null ]
selected = 0
[node name="HSeparator3" type="HSeparator" parent="VBoxContainer"]
margin_top = 426.0
margin_right = 516.0
margin_bottom = 430.0
[node name="Path" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 434.0
margin_right = 516.0
margin_bottom = 458.0
[node name="Label" type="Label" parent="VBoxContainer/Path"]
margin_top = 5.0
margin_right = 32.0
margin_bottom = 19.0
rect_min_size = Vector2( 30, 0 )
text = "Path:"
[node name="PathLineEdit" type="LineEdit" parent="VBoxContainer/Path"]
margin_left = 36.0
margin_right = 453.0
margin_bottom = 24.0
size_flags_horizontal = 3
align = 2
[node name="PathButton" type="Button" parent="VBoxContainer/Path"]
margin_left = 457.0
margin_right = 516.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
text = "Browse"
[node name="File" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 462.0
margin_right = 516.0
margin_bottom = 486.0
[node name="Label" type="Label" parent="VBoxContainer/File"]
margin_top = 5.0
margin_right = 30.0
margin_bottom = 19.0
rect_min_size = Vector2( 30, 0 )
text = "File:"
[node name="FileLineEdit" type="LineEdit" parent="VBoxContainer/File"]
margin_left = 34.0
margin_right = 376.0
margin_bottom = 24.0
size_flags_horizontal = 3
align = 2
[node name="FileFormat" type="OptionButton" parent="VBoxContainer/File"]
margin_left = 380.0
margin_right = 516.0
margin_bottom = 24.0
rect_min_size = Vector2( 130, 0 )
mouse_default_cursor_shape = 8
disabled = true
text = ".png; PNG Image"
items = [ ".png; PNG Image", null, false, 0, null, ".gif; GIF Image", null, false, 1, null ]
selected = 0
[node name="Popups" type="Node" parent="."]
[node name="PathDialog" type="FileDialog" parent="Popups"]
margin_left = 8.0
margin_top = 8.0
margin_right = 448.0
margin_bottom = 494.0
rect_min_size = Vector2( 440, 300 )
size_flags_horizontal = 0
size_flags_vertical = 0
window_title = "Otwórz katalog"
resizable = true
mode = 2
access = 2
current_dir = "E:/Projekty/Godot/Pixelorama"
current_path = "E:/Projekty/Godot/Pixelorama/"
[node name="PathValidationAlert" type="AcceptDialog" parent="Popups"]
margin_left = 8.0
margin_top = 180.0
margin_right = 448.0
margin_bottom = 280.0
size_flags_horizontal = 0
size_flags_vertical = 0
window_title = "Alarm!"
resizable = true
dialog_text = "Directory path or file name is not valid!"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="FileExistsAlert" type="AcceptDialog" parent="Popups"]
margin_left = 8.0
margin_top = 180.0
margin_right = 448.0
margin_bottom = 280.0
size_flags_horizontal = 0
size_flags_vertical = 0
window_title = "Alarm!"
resizable = true
dialog_text = "File %s already exists. Overwrite?"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="FrameTimer" type="Timer" parent="."]
__meta__ = {
"_editor_description_": "Timer to advance animation frames in animation preview."
}
[node name="GifExporter" type="Node" parent="."]
script = ExtResource( 2 )
__meta__ = {
"_editor_description_": ""
}
[connection signal="about_to_show" from="." to="." method="_on_ExportDialog_about_to_show"]
[connection signal="confirmed" from="." to="." method="_on_ExportDialog_confirmed"]
[connection signal="custom_action" from="." to="." method="_on_ExportDialog_custom_action"]
[connection signal="popup_hide" from="." to="." method="_on_ExportDialog_popup_hide"]
[connection signal="tab_clicked" from="VBoxContainer/Tabs" to="." method="_on_Tabs_tab_clicked"]
[connection signal="value_changed" from="VBoxContainer/FrameOptions/FrameNumber/FrameNumber" to="." method="_on_Frame_value_changed"]
[connection signal="item_selected" from="VBoxContainer/SpritesheetOptions/Orientation/Orientation" to="." method="_on_Orientation_item_selected"]
[connection signal="value_changed" from="VBoxContainer/SpritesheetOptions/Orientation/LinesCount" to="." method="_on_LinesCount_value_changed"]
[connection signal="item_selected" from="VBoxContainer/AnimationOptions/AnimationType" to="." method="_on_AnimationType_item_selected"]
[connection signal="toggled" from="VBoxContainer/AnimationOptions/MultipleAnimationsDirectories" to="." method="_on_MultipleAnimationsDirectories_toggled"]
[connection signal="color_changed" from="VBoxContainer/AnimationOptions/AnimatedOptions/BackgroundColor" to="." method="_on_BackgroundColor_color_changed"]
[connection signal="item_selected" from="VBoxContainer/AnimationOptions/AnimatedOptions/Direction" to="." method="_on_Direction_item_selected"]
[connection signal="value_changed" from="VBoxContainer/Options/Resize" to="." method="_on_Resize_value_changed"]
[connection signal="item_selected" from="VBoxContainer/Options/Interpolation" to="." method="_on_Interpolation_item_selected"]
[connection signal="text_changed" from="VBoxContainer/Path/PathLineEdit" to="." method="_on_PathLineEdit_text_changed"]
[connection signal="pressed" from="VBoxContainer/Path/PathButton" to="." method="_on_PathButton_pressed"]
[connection signal="text_changed" from="VBoxContainer/File/FileLineEdit" to="." method="_on_FileLineEdit_text_changed"]
[connection signal="item_selected" from="VBoxContainer/File/FileFormat" to="." method="_on_FileFormat_item_selected"]
[connection signal="dir_selected" from="Popups/PathDialog" to="." method="_on_FileDialog_dir_selected"]
[connection signal="confirmed" from="Popups/FileExistsAlert" to="." method="_on_FileExistsAlert_confirmed"]
[connection signal="custom_action" from="Popups/FileExistsAlert" to="." method="_on_FileExistsAlert_custom_action"]
[connection signal="timeout" from="FrameTimer" to="." method="_on_FrameTimer_timeout"]

View file

@ -0,0 +1,134 @@
extends AcceptDialog
var current_tag_id := 0
var tag_vboxes := []
var delete_tag_button : Button
onready var main_vbox_cont : VBoxContainer = $VBoxContainer/ScrollContainer/VBoxTagContainer
onready var add_tag_button : TextureButton = $VBoxContainer/ScrollContainer/VBoxTagContainer/AddTag
onready var options_dialog = $TagOptions
func _on_FrameTagDialog_about_to_show() -> void:
Global.can_draw = false
for vbox in tag_vboxes:
vbox.queue_free()
tag_vboxes.clear()
var i := 0
for tag in Global.animation_tags:
var vbox_cont := VBoxContainer.new()
var hbox_cont := HBoxContainer.new()
var tag_label := Label.new()
if tag[2] == tag[3]:
tag_label.text = "Tag %s (Frame %s)" % [i + 1, tag[2]]
else:
tag_label.text = "Tag %s (Frames %s-%s)" % [i + 1, tag[2], tag[3]]
hbox_cont.add_child(tag_label)
var edit_button := Button.new()
edit_button.text = "Edit"
edit_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
edit_button.connect("pressed", self, "_on_EditButton_pressed", [i])
hbox_cont.add_child(edit_button)
vbox_cont.add_child(hbox_cont)
var name_label := Label.new()
name_label.text = tag[0]
name_label.modulate = tag[1]
vbox_cont.add_child(name_label)
var hsep := HSeparator.new()
hsep.size_flags_horizontal = SIZE_EXPAND_FILL
vbox_cont.add_child(hsep)
main_vbox_cont.add_child(vbox_cont)
tag_vboxes.append(vbox_cont)
i += 1
add_tag_button.visible = true
main_vbox_cont.move_child(add_tag_button, main_vbox_cont.get_child_count() - 1)
func _on_FrameTagDialog_popup_hide() -> void:
Global.can_draw = true
func _on_AddTag_pressed() -> void:
options_dialog.popup_centered()
current_tag_id = Global.animation_tags.size()
options_dialog.get_node("GridContainer/FromSpinBox").value = Global.current_frame + 1
options_dialog.get_node("GridContainer/ToSpinBox").value = Global.current_frame + 1
func _on_EditButton_pressed(_tag_id : int) -> void:
options_dialog.popup_centered()
current_tag_id = _tag_id
options_dialog.get_node("GridContainer/NameLineEdit").text = Global.animation_tags[_tag_id][0]
options_dialog.get_node("GridContainer/ColorPickerButton").color = Global.animation_tags[_tag_id][1]
options_dialog.get_node("GridContainer/FromSpinBox").value = Global.animation_tags[_tag_id][2]
options_dialog.get_node("GridContainer/ToSpinBox").value = Global.animation_tags[_tag_id][3]
if !delete_tag_button:
delete_tag_button = options_dialog.add_button("Delete Tag", true, "delete_tag")
else:
delete_tag_button.visible = true
func _on_TagOptions_confirmed() -> void:
var tag_name : String = options_dialog.get_node("GridContainer/NameLineEdit").text
var tag_color : Color = options_dialog.get_node("GridContainer/ColorPickerButton").color
var tag_from : int = options_dialog.get_node("GridContainer/FromSpinBox").value
var tag_to : int = options_dialog.get_node("GridContainer/ToSpinBox").value
if tag_to > Global.canvases.size():
tag_to = Global.canvases.size()
if tag_from > tag_to:
tag_from = tag_to
var new_animation_tags := Global.animation_tags.duplicate(true)
if current_tag_id == Global.animation_tags.size():
new_animation_tags.append([tag_name, tag_color, tag_from, tag_to])
else:
new_animation_tags[current_tag_id][0] = tag_name
new_animation_tags[current_tag_id][1] = tag_color
new_animation_tags[current_tag_id][2] = tag_from
new_animation_tags[current_tag_id][3] = tag_to
# Handle Undo/Redo
Global.undos += 1
Global.undo_redo.create_action("Modify Frame Tag")
Global.undo_redo.add_do_method(Global, "general_redo")
Global.undo_redo.add_undo_method(Global, "general_undo")
Global.undo_redo.add_do_property(Global, "animation_tags", new_animation_tags)
Global.undo_redo.add_undo_property(Global, "animation_tags", Global.animation_tags)
Global.undo_redo.commit_action()
_on_FrameTagDialog_about_to_show()
func _on_TagOptions_custom_action(action : String) -> void:
if action == "delete_tag":
var new_animation_tags := Global.animation_tags.duplicate(true)
new_animation_tags.remove(current_tag_id)
# Handle Undo/Redo
Global.undos += 1
Global.undo_redo.create_action("Delete Frame Tag")
Global.undo_redo.add_do_method(Global, "general_redo")
Global.undo_redo.add_undo_method(Global, "general_undo")
Global.undo_redo.add_do_property(Global, "animation_tags", new_animation_tags)
Global.undo_redo.add_undo_property(Global, "animation_tags", Global.animation_tags)
Global.undo_redo.commit_action()
options_dialog.hide()
_on_FrameTagDialog_about_to_show()
func _on_TagOptions_popup_hide() -> void:
if delete_tag_button:
delete_tag_button.visible = false
func _on_PlayOnlyTags_toggled(button_pressed : bool) -> void:
Global.play_only_tags = button_pressed

View file

@ -0,0 +1,169 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://src/Dialogs/FrameTagDialog.gd" type="Script" id=1]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/new_frame.png" type="Texture" id=2]
[node name="FrameTagDialog" type="AcceptDialog"]
margin_right = 83.0
margin_bottom = 58.0
rect_min_size = Vector2( 400, 200 )
window_title = "Frame Tag Properties"
resizable = true
script = ExtResource( 1 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 8.0
margin_top = 8.0
margin_right = -8.0
margin_bottom = -36.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
margin_right = 384.0
margin_bottom = 128.0
size_flags_horizontal = 3
size_flags_vertical = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxTagContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
margin_right = 384.0
margin_bottom = 28.0
size_flags_horizontal = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/ScrollContainer/VBoxTagContainer"]
margin_right = 384.0
margin_bottom = 4.0
size_flags_horizontal = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AddTag" type="Button" parent="VBoxContainer/ScrollContainer/VBoxTagContainer" groups=[
"UIButtons",
]]
margin_top = 8.0
margin_right = 20.0
margin_bottom = 28.0
rect_min_size = Vector2( 20, 0 )
hint_tooltip = "Add a new frame tag"
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 0
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/ScrollContainer/VBoxTagContainer/AddTag"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -6.0
margin_top = -6.0
margin_right = 6.0
margin_bottom = 6.0
texture = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PlayOnlyTags" type="CheckBox" parent="VBoxContainer"]
margin_top = 132.0
margin_right = 333.0
margin_bottom = 156.0
hint_tooltip = "If it's selected, the animation plays only on the frames that have the same tag.
If it's not, the animation will play for all frames, ignoring tags."
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
pressed = true
text = "Animation plays only on frames of the same tag"
[node name="TagOptions" type="ConfirmationDialog" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 392.0
margin_bottom = 164.0
[node name="GridContainer" type="GridContainer" parent="TagOptions"]
margin_left = 8.0
margin_top = 8.0
margin_right = 376.0
margin_bottom = 120.0
custom_constants/vseparation = 8
custom_constants/hseparation = 8
columns = 4
__meta__ = {
"_edit_use_anchors_": false
}
[node name="NameLabel" type="Label" parent="TagOptions/GridContainer"]
margin_top = 5.0
margin_right = 42.0
margin_bottom = 19.0
text = "Name:"
[node name="NameLineEdit" type="LineEdit" parent="TagOptions/GridContainer"]
margin_left = 50.0
margin_right = 124.0
margin_bottom = 24.0
caret_blink = true
caret_blink_speed = 0.5
[node name="ColorLabel" type="Label" parent="TagOptions/GridContainer"]
margin_left = 132.0
margin_top = 5.0
margin_right = 169.0
margin_bottom = 19.0
text = "Color:"
[node name="ColorPickerButton" type="ColorPickerButton" parent="TagOptions/GridContainer"]
margin_left = 177.0
margin_right = 251.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
color = Color( 1, 0, 0, 1 )
[node name="FromLabel" type="Label" parent="TagOptions/GridContainer"]
margin_top = 37.0
margin_right = 42.0
margin_bottom = 51.0
text = "From:"
[node name="FromSpinBox" type="SpinBox" parent="TagOptions/GridContainer"]
margin_left = 50.0
margin_top = 32.0
margin_right = 124.0
margin_bottom = 56.0
mouse_default_cursor_shape = 2
min_value = 1.0
value = 1.0
[node name="ToLabel" type="Label" parent="TagOptions/GridContainer"]
margin_left = 132.0
margin_top = 37.0
margin_right = 169.0
margin_bottom = 51.0
text = "To:"
[node name="ToSpinBox" type="SpinBox" parent="TagOptions/GridContainer"]
margin_left = 177.0
margin_top = 32.0
margin_right = 251.0
margin_bottom = 56.0
mouse_default_cursor_shape = 2
min_value = 1.0
value = 1.0
[connection signal="about_to_show" from="." to="." method="_on_FrameTagDialog_about_to_show"]
[connection signal="popup_hide" from="." to="." method="_on_FrameTagDialog_popup_hide"]
[connection signal="pressed" from="VBoxContainer/ScrollContainer/VBoxTagContainer/AddTag" to="." method="_on_AddTag_pressed"]
[connection signal="toggled" from="VBoxContainer/PlayOnlyTags" to="." method="_on_PlayOnlyTags_toggled"]
[connection signal="confirmed" from="TagOptions" to="." method="_on_TagOptions_confirmed"]
[connection signal="custom_action" from="TagOptions" to="." method="_on_TagOptions_custom_action"]
[connection signal="popup_hide" from="TagOptions" to="." method="_on_TagOptions_popup_hide"]

100
src/Dialogs/HSVDialog.gd Normal file
View file

@ -0,0 +1,100 @@
extends WindowDialog
var current_layer : Image
var preview_image : Image
var preview_texture : ImageTexture
onready var hue_slider = $MarginContainer/VBoxContainer/HBoxContainer/Sliders/Hue
onready var sat_slider = $MarginContainer/VBoxContainer/HBoxContainer/Sliders/Saturation
onready var val_slider = $MarginContainer/VBoxContainer/HBoxContainer/Sliders/Value
onready var hue_spinbox = $MarginContainer/VBoxContainer/HBoxContainer/TextBoxes/Hue
onready var sat_spinbox = $MarginContainer/VBoxContainer/HBoxContainer/TextBoxes/Saturation
onready var val_spinbox = $MarginContainer/VBoxContainer/HBoxContainer/TextBoxes/Value
onready var preview = $MarginContainer/VBoxContainer/TextureRect
func _ready() -> void:
current_layer = Image.new()
preview_image = Image.new()
preview_texture = ImageTexture.new()
preview_texture.flags = 0
func _on_HSVDialog_about_to_show() -> void:
current_layer = Global.canvas.layers[Global.current_layer][0]
preview_image.copy_from(current_layer)
update_preview()
func _on_Cancel_pressed() -> void:
visible = false
reset()
func _on_Apply_pressed() -> void:
Global.canvas.handle_undo("Draw")
Global.canvas.adjust_hsv(current_layer,0,hue_slider.value)
Global.canvas.adjust_hsv(current_layer,1,sat_slider.value)
Global.canvas.adjust_hsv(current_layer,2,val_slider.value)
Global.canvas.update_texture(Global.current_layer)
Global.canvas.handle_redo("Draw")
reset()
visible = false
func reset() -> void:
disconnect_signals()
hue_slider.value = 0
sat_slider.value = 0
val_slider.value = 0
hue_spinbox.value = 0
sat_spinbox.value = 0
val_spinbox.value = 0
reconnect_signals()
func update_preview() -> void:
preview_image.copy_from(current_layer)
Global.canvas.adjust_hsv(preview_image,0,hue_slider.value)
Global.canvas.adjust_hsv(preview_image,1,sat_slider.value)
Global.canvas.adjust_hsv(preview_image,2,val_slider.value)
preview_texture.create_from_image(preview_image, 0)
preview.texture = preview_texture
func disconnect_signals() -> void:
hue_slider.disconnect("value_changed",self,"_on_Hue_value_changed")
sat_slider.disconnect("value_changed",self,"_on_Saturation_value_changed")
val_slider.disconnect("value_changed",self,"_on_Value_value_changed")
hue_spinbox.disconnect("value_changed",self,"_on_Hue_value_changed")
sat_spinbox.disconnect("value_changed",self,"_on_Saturation_value_changed")
val_spinbox.disconnect("value_changed",self,"_on_Value_value_changed")
func reconnect_signals() -> void:
hue_slider.connect("value_changed",self,"_on_Hue_value_changed")
sat_slider.connect("value_changed",self,"_on_Saturation_value_changed")
val_slider.connect("value_changed",self,"_on_Value_value_changed")
hue_spinbox.connect("value_changed",self,"_on_Hue_value_changed")
sat_spinbox.connect("value_changed",self,"_on_Saturation_value_changed")
val_spinbox.connect("value_changed",self,"_on_Value_value_changed")
func _on_Hue_value_changed(value : float) -> void:
hue_spinbox.value = value
hue_slider.value = value
update_preview()
func _on_Saturation_value_changed(value : float) -> void:
sat_spinbox.value = value
sat_slider.value = value
update_preview()
func _on_Value_value_changed(value : float) -> void:
val_spinbox.value = value
val_slider.value = value
update_preview()

167
src/Dialogs/HSVDialog.tscn Normal file
View file

@ -0,0 +1,167 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Dialogs/HSVDialog.gd" type="Script" id=1]
[node name="HSVDialog" type="WindowDialog"]
margin_left = 1.0
margin_top = -1.0
margin_right = 464.0
margin_bottom = 318.0
window_title = "Adjust HSV"
resizable = true
script = ExtResource( 1 )
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
custom_constants/margin_right = 5
custom_constants/margin_top = 5
custom_constants/margin_left = 5
custom_constants/margin_bottom = 5
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
margin_left = 5.0
margin_top = 5.0
margin_right = 458.0
margin_bottom = 314.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TextureRect" type="TextureRect" parent="MarginContainer/VBoxContainer"]
margin_right = 453.0
margin_bottom = 197.0
size_flags_vertical = 3
expand = true
stretch_mode = 6
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
margin_top = 201.0
margin_right = 453.0
margin_bottom = 285.0
custom_constants/separation = 10
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Names" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
margin_right = 82.0
margin_bottom = 84.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.9
custom_constants/separation = 8
[node name="Hue" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/Names"]
margin_right = 82.0
margin_bottom = 14.0
text = "Hue"
align = 2
[node name="Saturation" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/Names"]
margin_top = 22.0
margin_right = 82.0
margin_bottom = 36.0
text = "Saturation"
align = 2
[node name="Value" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer/Names"]
margin_top = 44.0
margin_right = 82.0
margin_bottom = 58.0
text = "Value"
align = 2
[node name="Sliders" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
margin_left = 92.0
margin_right = 368.0
margin_bottom = 84.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 3.0
custom_constants/separation = 7
[node name="Hue" type="HSlider" parent="MarginContainer/VBoxContainer/HBoxContainer/Sliders"]
margin_right = 276.0
margin_bottom = 16.0
mouse_default_cursor_shape = 2
min_value = -180.0
max_value = 180.0
[node name="Saturation" type="HSlider" parent="MarginContainer/VBoxContainer/HBoxContainer/Sliders"]
margin_top = 23.0
margin_right = 276.0
margin_bottom = 39.0
mouse_default_cursor_shape = 2
min_value = -100.0
[node name="Value" type="HSlider" parent="MarginContainer/VBoxContainer/HBoxContainer/Sliders"]
margin_top = 46.0
margin_right = 276.0
margin_bottom = 62.0
mouse_default_cursor_shape = 2
min_value = -100.0
[node name="TextBoxes" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
margin_left = 378.0
margin_right = 452.0
margin_bottom = 84.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.0
custom_constants/separation = 6
[node name="Hue" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer/TextBoxes"]
margin_right = 74.0
margin_bottom = 24.0
mouse_default_cursor_shape = 1
min_value = -180.0
max_value = 180.0
[node name="Saturation" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer/TextBoxes"]
margin_top = 30.0
margin_right = 74.0
margin_bottom = 54.0
mouse_default_cursor_shape = 1
min_value = -100.0
[node name="Value" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer/TextBoxes"]
margin_top = 60.0
margin_right = 74.0
margin_bottom = 84.0
mouse_default_cursor_shape = 1
min_value = -100.0
[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
margin_top = 289.0
margin_right = 453.0
margin_bottom = 309.0
custom_constants/separation = 16
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Apply" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
margin_right = 218.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Apply"
[node name="Cancel" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer2"]
margin_left = 234.0
margin_right = 453.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Cancel"
[connection signal="about_to_show" from="." to="." method="_on_HSVDialog_about_to_show"]
[connection signal="value_changed" from="MarginContainer/VBoxContainer/HBoxContainer/Sliders/Hue" to="." method="_on_Hue_value_changed"]
[connection signal="value_changed" from="MarginContainer/VBoxContainer/HBoxContainer/Sliders/Saturation" to="." method="_on_Saturation_value_changed"]
[connection signal="value_changed" from="MarginContainer/VBoxContainer/HBoxContainer/Sliders/Value" to="." method="_on_Value_value_changed"]
[connection signal="value_changed" from="MarginContainer/VBoxContainer/HBoxContainer/TextBoxes/Hue" to="." method="_on_Hue_value_changed"]
[connection signal="value_changed" from="MarginContainer/VBoxContainer/HBoxContainer/TextBoxes/Saturation" to="." method="_on_Saturation_value_changed"]
[connection signal="value_changed" from="MarginContainer/VBoxContainer/HBoxContainer/TextBoxes/Value" to="." method="_on_Value_value_changed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/Apply" to="." method="_on_Apply_pressed"]
[connection signal="pressed" from="MarginContainer/VBoxContainer/HBoxContainer2/Cancel" to="." method="_on_Cancel_pressed"]

View file

@ -0,0 +1,146 @@
extends FileDialog
var new_frame := true
var import_spritesheet := false
var spritesheet_horizontal := 1
var spritesheet_vertical := 1
func _ready() -> void:
var children := []
for i in range(get_child_count()):
if i > 7:
children.append(get_child(i))
for child in children:
remove_child(child)
get_vbox().add_child(child)
func _on_ImportAsNewFrame_pressed() -> void:
new_frame = !new_frame
func _on_ImportSpritesheet_pressed() -> void:
import_spritesheet = !import_spritesheet
var spritesheet_container = Global.find_node_by_name(self, "Spritesheet")
spritesheet_container.visible = import_spritesheet
func _on_HorizontalFrames_value_changed(value) -> void:
spritesheet_horizontal = value
func _on_VerticalFrames_value_changed(value) -> void:
spritesheet_vertical = value
func _on_ImportSprites_files_selected(paths : PoolStringArray) -> void:
Global.control.opensprite_file_selected = true
if !new_frame: # If we're not adding a new frame, delete the previous
Global.clear_canvases()
var first_path : String = paths[0]
var i : int = Global.canvases.size()
if !import_spritesheet:
# Find the biggest image and let it handle the camera zoom options
var max_size : Vector2
var biggest_canvas : Canvas
for path in paths:
var image := Image.new()
var err := image.load(path)
if err != OK: # An error occured
var file_name : String = path.get_file()
Global.error_dialog.set_text(tr("Can't load file '%s'.\nError code: %s") % [file_name, str(err)])
Global.error_dialog.popup_centered()
continue
var canvas : Canvas = load("res://src/Canvas.tscn").instance()
canvas.size = image.get_size()
image.convert(Image.FORMAT_RGBA8)
image.lock()
var tex := ImageTexture.new()
tex.create_from_image(image, 0)
# Store [Image, ImageTexture, Opacity]
canvas.layers.append([image, tex, 1])
for _i in range(1, Global.layers.size()):
var empty_sprite := Image.new()
empty_sprite.create(canvas.size.x, canvas.size.y, false, Image.FORMAT_RGBA8)
empty_sprite.fill(Color(0, 0, 0, 0))
empty_sprite.lock()
var empty_tex := ImageTexture.new()
empty_tex.create_from_image(empty_sprite, 0)
# Store [Image, ImageTexture, Opacity]
canvas.layers.append([empty_sprite, empty_tex, 1])
canvas.frame = i
Global.canvases.append(canvas)
Global.canvas_parent.add_child(canvas)
canvas.visible = false
if path == paths[0]: # If it's the first file
max_size = canvas.size
biggest_canvas = canvas
else:
if canvas.size > max_size:
biggest_canvas = canvas
i += 1
if biggest_canvas:
biggest_canvas.camera_zoom()
else:
var image := Image.new()
var err := image.load(first_path)
if err != OK: # An error occured
var file_name : String = first_path.get_file()
Global.error_dialog.set_text(tr("Can't load file '%s'.\nError code: %s") % [file_name, str(err)])
Global.error_dialog.popup_centered()
return
spritesheet_horizontal = min(spritesheet_horizontal, image.get_size().x)
spritesheet_vertical = min(spritesheet_vertical, image.get_size().y)
var frame_width := image.get_size().x / spritesheet_horizontal
var frame_height := image.get_size().y / spritesheet_vertical
for yy in range(spritesheet_vertical):
for xx in range(spritesheet_horizontal):
var canvas : Canvas = load("res://src/Canvas.tscn").instance()
var cropped_image := Image.new()
cropped_image = image.get_rect(Rect2(frame_width * xx, frame_height * yy, frame_width, frame_height))
canvas.size = cropped_image.get_size()
cropped_image.convert(Image.FORMAT_RGBA8)
cropped_image.lock()
var tex := ImageTexture.new()
tex.create_from_image(cropped_image, 0)
# Store [Image, ImageTexture, Opacity]
canvas.layers.append([cropped_image, tex, 1])
for _i in range(1, Global.layers.size()):
var empty_sprite := Image.new()
empty_sprite.create(canvas.size.x, canvas.size.y, false, Image.FORMAT_RGBA8)
empty_sprite.fill(Color(0, 0, 0, 0))
empty_sprite.lock()
var empty_tex := ImageTexture.new()
empty_tex.create_from_image(empty_sprite, 0)
# Store [Image, ImageTexture, Opacity]
canvas.layers.append([empty_sprite, empty_tex, 1])
canvas.frame = i
Global.canvases.append(canvas)
Global.canvas_parent.add_child(canvas)
canvas.visible = false
i += 1
Global.canvases[Global.canvases.size() - 1].camera_zoom()
Global.canvases = Global.canvases # Just to call Global.canvases_changed
Global.current_frame = i - 1
Global.canvas = Global.canvases[Global.canvases.size() - 1]
Global.canvas.visible = true
Global.window_title = first_path.get_file() + " (" + tr("imported") + ") - Pixelorama"

View file

@ -0,0 +1,77 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Dialogs/ImportSprites.gd" type="Script" id=1]
[node name="ImportSprites" type="FileDialog"]
margin_right = 515.0
margin_bottom = 348.0
window_title = "Open File(s)"
resizable = true
mode = 1
access = 2
filters = PoolStringArray( "*.bmp ; BMP Image", "*.hdr ; Radiance HDR Image", "*.jpg,*.jpeg ; JPEG Image", "*.png ; PNG Image", "*.svg ; SVG Image", "*.tga ; TGA Image", "*.webp ; WebP Image" )
current_dir = "C:/Users/Overloaded/Dropbox/Orama Founding Members/εταιρικα αρχεια/Godot Projects/Pixelorama"
current_path = "C:/Users/Overloaded/Dropbox/Orama Founding Members/εταιρικα αρχεια/Godot Projects/Pixelorama/"
script = ExtResource( 1 )
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 507.0
margin_bottom = 312.0
[node name="ImportAsNewFrame" type="CheckBox" parent="HBoxContainer2"]
margin_right = 161.0
margin_bottom = 304.0
mouse_default_cursor_shape = 2
pressed = true
text = "Import as new frame"
[node name="ImportSpritesheet" type="CheckBox" parent="HBoxContainer2"]
margin_left = 165.0
margin_right = 343.0
margin_bottom = 304.0
mouse_default_cursor_shape = 2
text = "Import as a spritesheet"
[node name="Spritesheet" type="HBoxContainer" parent="."]
visible = false
margin_left = 8.0
margin_top = 8.0
margin_right = 507.0
margin_bottom = 312.0
[node name="Label" type="Label" parent="Spritesheet"]
margin_top = 1.0
margin_right = 101.0
margin_bottom = 16.0
text = "Horizontal frames:"
[node name="HorizontalFrames" type="SpinBox" parent="Spritesheet"]
margin_left = 105.0
margin_right = 159.0
margin_bottom = 17.0
mouse_default_cursor_shape = 2
min_value = 1.0
value = 1.0
[node name="Label2" type="Label" parent="Spritesheet"]
margin_left = 163.0
margin_top = 1.0
margin_right = 248.0
margin_bottom = 16.0
text = "Vertical frames:"
[node name="VerticalFrames" type="SpinBox" parent="Spritesheet"]
margin_left = 252.0
margin_right = 306.0
margin_bottom = 17.0
mouse_default_cursor_shape = 2
min_value = 1.0
value = 1.0
[connection signal="files_selected" from="." to="." method="_on_ImportSprites_files_selected"]
[connection signal="pressed" from="HBoxContainer2/ImportAsNewFrame" to="." method="_on_ImportAsNewFrame_pressed"]
[connection signal="pressed" from="HBoxContainer2/ImportSpritesheet" to="." method="_on_ImportSpritesheet_pressed"]
[connection signal="value_changed" from="Spritesheet/HorizontalFrames" to="." method="_on_HorizontalFrames_value_changed"]
[connection signal="value_changed" from="Spritesheet/VerticalFrames" to="." method="_on_VerticalFrames_value_changed"]

View file

@ -0,0 +1,7 @@
[gd_scene format=2]
[node name="NoProjectEditedOrCreatedAlertDialog" type="AcceptDialog"]
dialog_text = "You haven't saved or opened any project in Pixelorama yet!"
__meta__ = {
"_edit_use_anchors_": false
}

View file

@ -0,0 +1,7 @@
[gd_scene format=2]
[node name="OpenLastProjectAlertDialog" type="AcceptDialog"]
margin_right = 209.0
margin_bottom = 58.0
window_title = "Alarm!"
dialog_text = "Cannot find last project file."

View file

@ -0,0 +1,146 @@
extends ConfirmationDialog
func _ready() -> void:
$OptionsContainer/OutlineColor.get_picker().presets_visible = false
func _on_OutlineDialog_confirmed() -> void:
var outline_color : Color = $OptionsContainer/OutlineColor.color
var thickness : int = $OptionsContainer/ThickValue.value
var diagonal : bool = $OptionsContainer/DiagonalCheckBox.pressed
var inside_image : bool = $OptionsContainer/InsideImageCheckBox.pressed
var image : Image = Global.canvas.layers[Global.current_layer][0]
if image.is_invisible():
return
var new_image := Image.new()
new_image.copy_from(image)
new_image.lock()
Global.canvas.handle_undo("Draw")
for xx in image.get_size().x:
for yy in image.get_size().y:
var pos = Vector2(xx, yy)
var current_pixel := image.get_pixelv(pos)
if current_pixel.a == 0:
continue
for i in range(1, thickness + 1):
if inside_image:
var outline_pos : Vector2 = pos + Vector2.LEFT # Left
if outline_pos.x < 0 || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + Vector2.RIGHT * (i - 1)
if new_pos.x < Global.canvas.size.x:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
outline_pos = pos + Vector2.RIGHT # Right
if outline_pos.x >= Global.canvas.size.x || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + Vector2.LEFT * (i - 1)
if new_pos.x >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
outline_pos = pos + Vector2.UP # Up
if outline_pos.y < 0 || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + Vector2.DOWN * (i - 1)
if new_pos.y < Global.canvas.size.y:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
outline_pos = pos + Vector2.DOWN # Down
if outline_pos.y >= Global.canvas.size.y || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + Vector2.UP * (i - 1)
if new_pos.y >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
if diagonal:
outline_pos = pos + (Vector2.LEFT + Vector2.UP) # Top left
if (outline_pos.x < 0 && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.DOWN) * (i - 1)
if new_pos.x < Global.canvas.size.x && new_pos.y < Global.canvas.size.y:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
outline_pos = pos + (Vector2.LEFT + Vector2.DOWN) # Bottom left
if (outline_pos.x < 0 && outline_pos.y >= Global.canvas.size.y) || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + (Vector2.RIGHT + Vector2.UP) * (i - 1)
if new_pos.x < Global.canvas.size.x && new_pos.y >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
outline_pos = pos + (Vector2.RIGHT + Vector2.UP) # Top right
if (outline_pos.x >= Global.canvas.size.x && outline_pos.y < 0) || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.DOWN) * (i - 1)
if new_pos.x >= 0 && new_pos.y < Global.canvas.size.y:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
outline_pos = pos + (Vector2.RIGHT + Vector2.DOWN) # Bottom right
if (outline_pos.x >= Global.canvas.size.x && outline_pos.y >= Global.canvas.size.y) || image.get_pixelv(outline_pos).a == 0:
var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.UP) * (i - 1)
if new_pos.x >= 0 && new_pos.y >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a > 0:
new_image.set_pixelv(new_pos, outline_color)
else:
var new_pos : Vector2 = pos + Vector2.LEFT * i # Left
if new_pos.x >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
new_pos = pos + Vector2.RIGHT * i # Right
if new_pos.x < Global.canvas.size.x:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
new_pos = pos + Vector2.UP * i # Up
if new_pos.y >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
new_pos = pos + Vector2.DOWN * i # Down
if new_pos.y < Global.canvas.size.y:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
if diagonal:
new_pos = pos + (Vector2.LEFT + Vector2.UP) * i # Top left
if new_pos.x >= 0 && new_pos.y >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
new_pos = pos + (Vector2.LEFT + Vector2.DOWN) * i # Bottom left
if new_pos.x >= 0 && new_pos.y < Global.canvas.size.y:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
new_pos = pos + (Vector2.RIGHT + Vector2.UP) * i # Top right
if new_pos.x < Global.canvas.size.x && new_pos.y >= 0:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
new_pos = pos + (Vector2.RIGHT + Vector2.DOWN) * i # Bottom right
if new_pos.x < Global.canvas.size.x && new_pos.y < Global.canvas.size.y:
var new_pixel = image.get_pixelv(new_pos)
if new_pixel.a == 0:
new_image.set_pixelv(new_pos, outline_color)
image.copy_from(new_image)
Global.canvas.handle_redo("Draw")

View file

@ -0,0 +1,69 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Dialogs/OutlineDialog.gd" type="Script" id=1]
[node name="OutlineDialog" type="ConfirmationDialog"]
visible = true
margin_right = 200.0
margin_bottom = 70.0
script = ExtResource( 1 )
[node name="OptionsContainer" type="GridContainer" parent="."]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -121.0
margin_top = -52.0
margin_right = 121.0
margin_bottom = 24.0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="ThickLabel" type="Label" parent="OptionsContainer"]
margin_top = 5.0
margin_right = 90.0
margin_bottom = 19.0
text = "Thickness:"
[node name="ThickValue" type="SpinBox" parent="OptionsContainer"]
margin_left = 94.0
margin_right = 242.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 1.0
suffix = "px"
[node name="OutlineColorLabel" type="Label" parent="OptionsContainer"]
margin_top = 31.0
margin_right = 90.0
margin_bottom = 45.0
text = "Fill with color:"
[node name="OutlineColor" type="ColorPickerButton" parent="OptionsContainer"]
margin_left = 94.0
margin_top = 28.0
margin_right = 242.0
margin_bottom = 48.0
rect_min_size = Vector2( 64, 20 )
color = Color( 1, 0, 0, 1 )
[node name="DiagonalCheckBox" type="CheckBox" parent="OptionsContainer"]
margin_top = 52.0
margin_right = 90.0
margin_bottom = 76.0
mouse_default_cursor_shape = 2
text = "Diagonal"
[node name="InsideImageCheckBox" type="CheckBox" parent="OptionsContainer"]
margin_left = 94.0
margin_top = 52.0
margin_right = 242.0
margin_bottom = 76.0
mouse_default_cursor_shape = 2
text = "Place inside image"
[connection signal="confirmed" from="." to="." method="_on_OutlineDialog_confirmed"]

View file

@ -0,0 +1,555 @@
extends AcceptDialog
onready var tree : Tree = $HSplitContainer/Tree
onready var right_side : VBoxContainer = $HSplitContainer/ScrollContainer/VBoxContainer
onready var general = $HSplitContainer/ScrollContainer/VBoxContainer/General
onready var languages = $HSplitContainer/ScrollContainer/VBoxContainer/Languages
onready var themes = $HSplitContainer/ScrollContainer/VBoxContainer/Themes
onready var canvas = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas
onready var image = $HSplitContainer/ScrollContainer/VBoxContainer/Image
onready var shortcuts = $HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts
onready var open_last_project_button = $HSplitContainer/ScrollContainer/VBoxContainer/General/OpenLastProject
onready var smooth_zoom_button = $HSplitContainer/ScrollContainer/VBoxContainer/General/SmoothZoom
onready var sensitivity_option = $HSplitContainer/ScrollContainer/VBoxContainer/General/PressureSentivity/PressureSensitivityOptionButton
onready var left_tool_icon = $HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/LeftToolIconCheckbox
onready var right_tool_icon = $HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/RightToolIconCheckbox
onready var default_width_value = $HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions/ImageDefaultWidth
onready var default_height_value = $HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions/ImageDefaultHeight
onready var default_fill_color = $HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions/DefaultFillColor
onready var grid_width_value = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridWidthValue
onready var grid_height_value = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridHeightValue
onready var grid_color = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridColor
onready var guide_color = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GuideOptions/GuideColor
onready var checker_size_value = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions/CheckerSizeValue
onready var checker_color_1 = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions/CheckerColor1
onready var checker_color_2 = $HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions/CheckerColor2
# Shortcuts
onready var theme_font_color : Color = $Popups/ShortcutSelector/EnteredShortcut.get_color("font_color")
var default_shortcuts_preset := {}
var custom_shortcuts_preset := {}
var action_being_edited := ""
var shortcut_already_assigned = false
var old_input_event : InputEventKey
var new_input_event : InputEventKey
func _ready() -> void:
# Disable input until the shortcut selector is displayed
set_process_input(false)
# Replace OK with Close since preference changes are being applied immediately, not after OK confirmation
get_ok().text = tr("Close")
for child in languages.get_children():
if child is Button:
child.connect("pressed", self, "_on_Language_pressed", [child])
child.hint_tooltip = child.name
for child in themes.get_children():
if child is Button:
child.connect("pressed", self, "_on_Theme_pressed", [child])
if Global.config_cache.has_section_key("preferences", "theme"):
var theme_id = Global.config_cache.get_value("preferences", "theme")
change_theme(theme_id)
themes.get_child(theme_id).pressed = true
else:
change_theme(0)
themes.get_child(0).pressed = true
# Set default values for General options
if Global.config_cache.has_section_key("preferences", "open_last_project"):
Global.open_last_project = Global.config_cache.get_value("preferences", "open_last_project")
open_last_project_button.pressed = Global.open_last_project
if Global.config_cache.has_section_key("preferences", "smooth_zoom"):
Global.smooth_zoom = Global.config_cache.get_value("preferences", "smooth_zoom")
smooth_zoom_button.pressed = Global.smooth_zoom
if Global.config_cache.has_section_key("preferences", "pressure_sensitivity"):
Global.pressure_sensitivity_mode = Global.config_cache.get_value("preferences", "pressure_sensitivity")
sensitivity_option.selected = Global.pressure_sensitivity_mode
if Global.config_cache.has_section_key("preferences", "show_left_tool_icon"):
Global.show_left_tool_icon = Global.config_cache.get_value("preferences", "show_left_tool_icon")
left_tool_icon.pressed = Global.show_left_tool_icon
if Global.config_cache.has_section_key("preferences", "show_right_tool_icon"):
Global.show_right_tool_icon = Global.config_cache.get_value("preferences", "show_right_tool_icon")
right_tool_icon.pressed = Global.show_right_tool_icon
# Get autosave settings
if Global.config_cache.has_section_key("preferences", "autosave_interval"):
var autosave_interval = Global.config_cache.get_value("preferences", "autosave_interval")
OpenSave.set_autosave_interval(autosave_interval)
general.get_node("AutosaveInterval/AutosaveInterval").value = autosave_interval
if Global.config_cache.has_section_key("preferences", "enable_autosave"):
var enable_autosave = Global.config_cache.get_value("preferences", "enable_autosave")
OpenSave.toggle_autosave(enable_autosave)
general.get_node("EnableAutosave").pressed = enable_autosave
# Set default values for Canvas options
if Global.config_cache.has_section_key("preferences", "grid_size"):
var grid_size = Global.config_cache.get_value("preferences", "grid_size")
Global.grid_width = int(grid_size.x)
Global.grid_height = int(grid_size.y)
grid_width_value.value = grid_size.x
grid_height_value.value = grid_size.y
if Global.config_cache.has_section_key("preferences", "grid_color"):
Global.grid_color = Global.config_cache.get_value("preferences", "grid_color")
grid_color.color = Global.grid_color
if Global.config_cache.has_section_key("preferences", "checker_size"):
var checker_size = Global.config_cache.get_value("preferences", "checker_size")
Global.checker_size = int(checker_size)
checker_size_value.value = checker_size
if Global.config_cache.has_section_key("preferences", "checker_color_1"):
Global.checker_color_1 = Global.config_cache.get_value("preferences", "checker_color_1")
checker_color_1.color = Global.checker_color_1
if Global.config_cache.has_section_key("preferences", "checker_color_2"):
Global.checker_color_2 = Global.config_cache.get_value("preferences", "checker_color_2")
checker_color_2.color = Global.checker_color_2
Global.transparent_checker._ready()
if Global.config_cache.has_section_key("preferences", "guide_color"):
Global.guide_color = Global.config_cache.get_value("preferences", "guide_color")
for canvas in Global.canvases:
for guide in canvas.get_children():
if guide is Guide:
guide.default_color = Global.guide_color
guide_color.color = Global.guide_color
# Set default values for Image
if Global.config_cache.has_section_key("preferences", "default_width"):
var default_width = Global.config_cache.get_value("preferences", "default_width")
Global.default_image_width = int(default_width)
default_width_value.value = Global.default_image_width
if Global.config_cache.has_section_key("preferences", "default_height"):
var default_height = Global.config_cache.get_value("preferences", "default_height")
Global.default_image_height = int(default_height)
default_height_value.value = Global.default_image_height
if Global.config_cache.has_section_key("preferences", "default_fill_color"):
var fill_color = Global.config_cache.get_value("preferences", "default_fill_color")
Global.default_fill_color = fill_color
default_fill_color.color = Global.default_fill_color
guide_color.get_picker().presets_visible = false
grid_color.get_picker().presets_visible = false
checker_color_1.get_picker().presets_visible = false
checker_color_2.get_picker().presets_visible = false
default_fill_color.get_picker().presets_visible = false
# Get default preset for shortcuts from project input map
# Buttons in shortcuts selector should be called the same as actions
for shortcut_grid_item in shortcuts.get_node("Shortcuts").get_children():
if shortcut_grid_item is Button:
var input_events = InputMap.get_action_list(shortcut_grid_item.name)
if input_events.size() > 1:
printerr("Every shortcut action should have just one input event assigned in input map")
shortcut_grid_item.text = (input_events[0] as InputEventKey).as_text()
shortcut_grid_item.connect("pressed", self, "_on_Shortcut_button_pressed", [shortcut_grid_item])
default_shortcuts_preset[shortcut_grid_item.name] = input_events[0]
# Load custom shortcuts from the config file
custom_shortcuts_preset = default_shortcuts_preset.duplicate()
for action in default_shortcuts_preset:
var saved_input_event = Global.config_cache.get_value("shortcuts", action, 0)
if saved_input_event is InputEventKey:
custom_shortcuts_preset[action] = saved_input_event
var shortcuts_preset = Global.config_cache.get_value("shortcuts", "shortcuts_preset", 0)
shortcuts.get_node("HBoxContainer/PresetOptionButton").select(shortcuts_preset)
_on_PresetOptionButton_item_selected(shortcuts_preset)
func _input(event : InputEvent) -> void:
if event is InputEventKey:
if event.pressed:
if event.scancode == KEY_ESCAPE:
$Popups/ShortcutSelector.hide()
else:
# Check if shortcut was already used
for action in InputMap.get_actions():
for input_event in InputMap.get_action_list(action):
if input_event is InputEventKey:
if OS.get_scancode_string(input_event.get_scancode_with_modifiers()) == OS.get_scancode_string(event.get_scancode_with_modifiers()):
$Popups/ShortcutSelector/EnteredShortcut.text = tr("Already assigned")
$Popups/ShortcutSelector/EnteredShortcut.add_color_override("font_color", Color.crimson)
get_tree().set_input_as_handled()
shortcut_already_assigned = true
return
# Store new shortcut
shortcut_already_assigned = false
old_input_event = InputMap.get_action_list(action_being_edited)[0]
new_input_event = event
$Popups/ShortcutSelector/EnteredShortcut.text = OS.get_scancode_string(event.get_scancode_with_modifiers())
$Popups/ShortcutSelector/EnteredShortcut.add_color_override("font_color", theme_font_color)
get_tree().set_input_as_handled()
func _on_PreferencesDialog_about_to_show(changed_language := false) -> void:
var root := tree.create_item()
var general_button := tree.create_item(root)
var language_button := tree.create_item(root)
var theme_button := tree.create_item(root)
var canvas_button := tree.create_item(root)
var image_button := tree.create_item(root)
var shortcuts_button := tree.create_item(root)
general_button.set_text(0, " " + tr("General"))
# We use metadata to avoid being affected by translations
general_button.set_metadata(0, "General")
language_button.set_text(0, " " + tr("Language"))
language_button.set_metadata(0, "Language")
theme_button.set_text(0, " " + tr("Themes"))
theme_button.set_metadata(0, "Themes")
canvas_button.set_text(0, " " + tr("Canvas"))
canvas_button.set_metadata(0, "Canvas")
image_button.set_text(0, " " + tr("Image"))
image_button.set_metadata(0, "Image")
shortcuts_button.set_text(0, " " + tr("Shortcuts"))
shortcuts_button.set_metadata(0, "Shortcuts")
if changed_language:
language_button.select(0)
else:
general_button.select(0)
func _on_PreferencesDialog_popup_hide() -> void:
tree.clear()
func _on_Tree_item_selected() -> void:
for child in right_side.get_children():
child.visible = false
var selected : String = tree.get_selected().get_metadata(0)
if "General" in selected:
general.visible = true
elif "Language" in selected:
languages.visible = true
elif "Themes" in selected:
themes.visible = true
elif "Canvas" in selected:
canvas.visible = true
elif "Image" in selected:
image.visible = true
elif "Shortcuts" in selected:
shortcuts.visible = true
func _on_PressureSensitivityOptionButton_item_selected(id : int) -> void:
Global.pressure_sensitivity_mode = id
Global.config_cache.set_value("preferences", "pressure_sensitivity", id)
Global.config_cache.save("user://cache.ini")
func _on_SmoothZoom_pressed() -> void:
Global.smooth_zoom = !Global.smooth_zoom
Global.config_cache.set_value("preferences", "smooth_zoom", Global.smooth_zoom)
Global.config_cache.save("user://cache.ini")
func _on_Language_pressed(button : Button) -> void:
var index := 0
var i := -1
for child in languages.get_children():
if child is Button:
if child == button:
button.pressed = true
index = i
else:
child.pressed = false
i += 1
if index == -1:
TranslationServer.set_locale(OS.get_locale())
else:
TranslationServer.set_locale(Global.loaded_locales[index])
if "zh" in TranslationServer.get_locale():
Global.control.theme.default_font = preload("res://Assets/Fonts/CJK/NotoSansCJKtc-Regular.tres")
else:
Global.control.theme.default_font = preload("res://Assets/Fonts/Roboto-Regular.tres")
Global.config_cache.set_value("preferences", "locale", TranslationServer.get_locale())
Global.config_cache.save("user://cache.ini")
# Update Translations
Global.update_hint_tooltips()
_on_PreferencesDialog_popup_hide()
_on_PreferencesDialog_about_to_show(true)
func _on_Theme_pressed(button : Button) -> void:
var index := 0
var i := 0
for child in themes.get_children():
if child is Button:
if child == button:
button.pressed = true
index = i
else:
child.pressed = false
i += 1
change_theme(index)
Global.config_cache.set_value("preferences", "theme", index)
Global.config_cache.save("user://cache.ini")
func change_theme(ID : int) -> void:
var font = Global.control.theme.default_font
var main_theme
var top_menu_style
var ruler_style
if ID == 0: # Dark Theme
Global.theme_type = "Dark"
VisualServer.set_default_clear_color(Color(0.247059, 0.25098, 0.247059))
main_theme = preload("res://Themes & Styles/Dark Theme/Dark Theme.tres")
top_menu_style = preload("res://Themes & Styles/Dark Theme/DarkTopMenuStyle.tres")
ruler_style = preload("res://Themes & Styles/Dark Theme/DarkRulerStyle.tres")
elif ID == 1: # Gray Theme
Global.theme_type = "Dark"
VisualServer.set_default_clear_color(Color(0.301961, 0.301961, 0.301961))
main_theme = preload("res://Themes & Styles/Gray Theme/Gray Theme.tres")
top_menu_style = preload("res://Themes & Styles/Gray Theme/GrayTopMenuStyle.tres")
ruler_style = preload("res://Themes & Styles/Dark Theme/DarkRulerStyle.tres")
elif ID == 2: # Godot's Theme
Global.theme_type = "Dark"
VisualServer.set_default_clear_color(Color(0.27451, 0.278431, 0.305882))
main_theme = preload("res://Themes & Styles/Godot\'s Theme/Godot\'s Theme.tres")
top_menu_style = preload("res://Themes & Styles/Godot\'s Theme/TopMenuStyle.tres")
ruler_style = preload("res://Themes & Styles/Godot\'s Theme/RulerStyle.tres")
elif ID == 3: # Gold Theme
Global.theme_type = "Gold"
VisualServer.set_default_clear_color(Color(0.694118, 0.619608, 0.458824))
main_theme = preload("res://Themes & Styles/Gold Theme/Gold Theme.tres")
top_menu_style = preload("res://Themes & Styles/Gold Theme/GoldTopMenuStyle.tres")
ruler_style = preload("res://Themes & Styles/Gold Theme/GoldRulerStyle.tres")
elif ID == 4: # Light Theme
Global.theme_type = "Light"
VisualServer.set_default_clear_color(Color(0.705882, 0.705882, 0.705882))
main_theme = preload("res://Themes & Styles/Light Theme/Light Theme.tres")
top_menu_style = preload("res://Themes & Styles/Light Theme/LightTopMenuStyle.tres")
ruler_style = preload("res://Themes & Styles/Light Theme/LightRulerStyle.tres")
Global.control.theme = main_theme
Global.control.theme.default_font = font
Global.top_menu_container.add_stylebox_override("panel", top_menu_style)
Global.horizontal_ruler.add_stylebox_override("normal", ruler_style)
Global.horizontal_ruler.add_stylebox_override("pressed", ruler_style)
Global.horizontal_ruler.add_stylebox_override("hover", ruler_style)
Global.horizontal_ruler.add_stylebox_override("focus", ruler_style)
Global.vertical_ruler.add_stylebox_override("normal", ruler_style)
Global.vertical_ruler.add_stylebox_override("pressed", ruler_style)
Global.vertical_ruler.add_stylebox_override("hover", ruler_style)
Global.vertical_ruler.add_stylebox_override("focus", ruler_style)
for button in get_tree().get_nodes_in_group("UIButtons"):
if button is TextureButton:
var last_backslash = button.texture_normal.resource_path.get_base_dir().find_last("/")
var button_category = button.texture_normal.resource_path.get_base_dir().right(last_backslash + 1)
var normal_file_name = button.texture_normal.resource_path.get_file()
button.texture_normal = load("res://Assets/Graphics/%s Themes/%s/%s" % [Global.theme_type, button_category, normal_file_name])
if button.texture_pressed:
var pressed_file_name = button.texture_pressed.resource_path.get_file()
button.texture_pressed = load("res://Assets/Graphics/%s Themes/%s/%s" % [Global.theme_type, button_category, pressed_file_name])
if button.texture_hover:
var hover_file_name = button.texture_hover.resource_path.get_file()
button.texture_hover = load("res://Assets/Graphics/%s Themes/%s/%s" % [Global.theme_type, button_category, hover_file_name])
if button.texture_disabled:
var disabled_file_name = button.texture_disabled.resource_path.get_file()
button.texture_disabled = load("res://Assets/Graphics/%s Themes/%s/%s" % [Global.theme_type, button_category, disabled_file_name])
elif button is Button:
var theme_type := Global.theme_type
if theme_type == "Gold":
theme_type = "Light"
var texture : TextureRect = button.get_child(0)
var last_backslash = texture.texture.resource_path.get_base_dir().find_last("/")
var button_category = texture.texture.resource_path.get_base_dir().right(last_backslash + 1)
var normal_file_name = texture.texture.resource_path.get_file()
texture.texture = load("res://Assets/Graphics/%s Themes/%s/%s" % [theme_type, button_category, normal_file_name])
# Make sure the frame text gets updated
Global.current_frame = Global.current_frame
$Popups/ShortcutSelector.theme = main_theme
func apply_shortcuts_preset(preset) -> void:
for action in preset:
var old_input_event : InputEventKey = InputMap.get_action_list(action)[0]
set_action_shortcut(action, old_input_event, preset[action])
shortcuts.get_node("Shortcuts/" + action).text = OS.get_scancode_string(preset[action].get_scancode_with_modifiers())
func toggle_shortcut_buttons(enabled : bool) -> void:
for shortcut_grid_item in shortcuts.get_node("Shortcuts").get_children():
if shortcut_grid_item is Button:
shortcut_grid_item.disabled = not enabled
if shortcut_grid_item.disabled:
shortcut_grid_item.mouse_default_cursor_shape = Control.CURSOR_FORBIDDEN
else:
shortcut_grid_item.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
func set_action_shortcut(action : String, old_input : InputEventKey, new_input : InputEventKey) -> void:
InputMap.action_erase_event(action, old_input)
InputMap.action_add_event(action, new_input)
Global.update_hint_tooltips()
# Set shortcut to switch colors button
if action == "switch_colors":
Global.color_switch_button.shortcut.shortcut = InputMap.get_action_list("switch_colors")[0]
func _on_GridWidthValue_value_changed(value : float) -> void:
Global.grid_width = value
Global.canvas.update()
Global.config_cache.set_value("preferences", "grid_size", Vector2(value, grid_height_value.value))
Global.config_cache.save("user://cache.ini")
func _on_GridHeightValue_value_changed(value : float) -> void:
Global.grid_height = value
Global.canvas.update()
Global.config_cache.set_value("preferences", "grid_size", Vector2(grid_width_value.value, value))
Global.config_cache.save("user://cache.ini")
func _on_GridColor_color_changed(color : Color) -> void:
Global.grid_color = color
Global.canvas.update()
Global.config_cache.set_value("preferences", "grid_color", color)
Global.config_cache.save("user://cache.ini")
func _on_CheckerSize_value_changed(value : float) -> void:
Global.checker_size = value
Global.transparent_checker._ready()
Global.config_cache.set_value("preferences", "checker_size", value)
Global.config_cache.save("user://cache.ini")
func _on_CheckerColor1_color_changed(color : Color) -> void:
Global.checker_color_1 = color
Global.transparent_checker._ready()
Global.config_cache.set_value("preferences", "checker_color_1", color)
Global.config_cache.save("user://cache.ini")
func _on_CheckerColor2_color_changed(color : Color) -> void:
Global.checker_color_2 = color
Global.transparent_checker._ready()
Global.config_cache.set_value("preferences", "checker_color_2", color)
Global.config_cache.save("user://cache.ini")
func _on_GuideColor_color_changed(color : Color) -> void:
Global.guide_color = color
for canvas in Global.canvases:
for guide in canvas.get_children():
if guide is Guide:
guide.default_color = color
Global.config_cache.set_value("preferences", "guide_color", color)
Global.config_cache.save("user://cache.ini")
func _on_ImageDefaultWidth_value_changed(value: float) -> void:
Global.default_image_width = value
Global.config_cache.set_value("preferences", "default_width", value)
Global.config_cache.save("user://cache.ini")
func _on_ImageDefaultHeight_value_changed(value: float) -> void:
Global.default_image_height = value
Global.config_cache.set_value("preferences", "default_height", value)
Global.config_cache.save("user://cache.ini")
func _on_DefaultBackground_color_changed(color: Color) -> void:
Global.default_fill_color = color
Global.config_cache.set_value("preferences", "default_fill_color", color)
Global.config_cache.save("user://cache.ini")
func _on_LeftIndicatorCheckbox_toggled(button_pressed : bool) -> void:
Global.left_square_indicator_visible = button_pressed
func _on_RightIndicatorCheckbox_toggled(button_pressed : bool) -> void:
Global.right_square_indicator_visible = button_pressed
func _on_LeftToolIconCheckbox_toggled(button_pressed : bool) -> void:
Global.show_left_tool_icon = button_pressed
Global.config_cache.set_value("preferences", "show_left_tool_icon", Global.show_left_tool_icon)
Global.config_cache.save("user://cache.ini")
func _on_RightToolIconCheckbox_toggled(button_pressed : bool) -> void:
Global.show_right_tool_icon = button_pressed
Global.config_cache.set_value("preferences", "show_right_tool_icon", Global.show_right_tool_icon)
Global.config_cache.save("user://cache.ini")
func _on_Shortcut_button_pressed(button : Button) -> void:
set_process_input(true)
action_being_edited = button.name
new_input_event = InputMap.get_action_list(button.name)[0]
shortcut_already_assigned = true
$Popups/ShortcutSelector.popup_centered()
func _on_ShortcutSelector_popup_hide() -> void:
set_process_input(false)
$Popups/ShortcutSelector/EnteredShortcut.text = ""
func _on_PresetOptionButton_item_selected(id : int) -> void:
# Only custom preset which is modifiable
toggle_shortcut_buttons(true if id == 1 else false)
match id:
0:
apply_shortcuts_preset(default_shortcuts_preset)
1:
apply_shortcuts_preset(custom_shortcuts_preset)
Global.config_cache.set_value("shortcuts", "shortcuts_preset", id)
Global.config_cache.save("user://cache.ini")
func _on_ShortcutSelector_confirmed() -> void:
if not shortcut_already_assigned:
set_action_shortcut(action_being_edited, old_input_event, new_input_event)
custom_shortcuts_preset[action_being_edited] = new_input_event
Global.config_cache.set_value("shortcuts", action_being_edited, new_input_event)
Global.config_cache.save("user://cache.ini")
shortcuts.get_node("Shortcuts/" + action_being_edited).text = OS.get_scancode_string(new_input_event.get_scancode_with_modifiers())
$Popups/ShortcutSelector.hide()
func _on_OpenLastProject_pressed() -> void:
Global.open_last_project = !Global.open_last_project
Global.config_cache.set_value("preferences", "open_last_project", Global.open_last_project)
Global.config_cache.save("user://cache.ini")
func _on_EnableAutosave_toggled(button_pressed : bool) -> void:
OpenSave.toggle_autosave(button_pressed)
Global.config_cache.set_value("preferences", "enable_autosave", button_pressed)
Global.config_cache.save("user://cache.ini")
func _on_AutosaveInterval_value_changed(value : float) -> void:
OpenSave.set_autosave_interval(value)
Global.config_cache.set_value("preferences", "autosave_interval", value)
Global.config_cache.save("user://cache.ini")

View file

@ -0,0 +1,874 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://src/Dialogs/PreferencesDialog.gd" type="Script" id=1]
[ext_resource path="res://Assets/Fonts/Roboto-Regular.tres" type="DynamicFont" id=2]
[ext_resource path="res://Assets/Fonts/CJK/NotoSansCJKtc-Regular.tres" type="DynamicFont" id=3]
[node name="PreferencesDialog" type="AcceptDialog"]
margin_left = -3.0
margin_top = 9.0
margin_right = 419.0
margin_bottom = 1163.0
rect_min_size = Vector2( 422, 340 )
window_title = "Preferences"
resizable = true
script = ExtResource( 1 )
__meta__ = {
"_edit_horizontal_guides_": [ ],
"_edit_use_anchors_": false,
"_edit_vertical_guides_": [ ]
}
[node name="HSplitContainer" type="HSplitContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 8.0
margin_top = 8.0
margin_right = -8.0
margin_bottom = -36.0
size_flags_horizontal = 3
custom_constants/autohide = 0
split_offset = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Tree" type="Tree" parent="HSplitContainer"]
margin_right = 86.0
margin_bottom = 1110.0
rect_min_size = Vector2( 85, 0 )
custom_constants/item_margin = -2
hide_root = true
[node name="ScrollContainer" type="ScrollContainer" parent="HSplitContainer"]
margin_left = 98.0
margin_right = 406.0
margin_bottom = 1110.0
rect_min_size = Vector2( 100, 0 )
size_flags_horizontal = 3
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/ScrollContainer"]
margin_right = 308.0
margin_bottom = 1230.0
size_flags_horizontal = 3
[node name="General" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"]
margin_right = 306.0
margin_bottom = 180.0
[node name="SmoothZoom" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_right = 306.0
margin_bottom = 24.0
hint_tooltip = "Adds a smoother transition when zooming in or out"
mouse_default_cursor_shape = 2
pressed = true
text = "Smooth Zoom"
[node name="HSeparator2" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_top = 28.0
margin_right = 306.0
margin_bottom = 32.0
[node name="GridContainer" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_top = 36.0
margin_right = 306.0
margin_bottom = 88.0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="LeftIndicatorCheckbox" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer"]
margin_right = 147.0
margin_bottom = 24.0
hint_tooltip = "Show left mouse pixel indicator or brush on the canvas when drawing"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
pressed = true
text = "Left pixel indicator"
[node name="RightIndicatorCheckbox" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer"]
margin_left = 151.0
margin_right = 306.0
margin_bottom = 24.0
hint_tooltip = "Show right mouse pixel indicator or brush on the canvas when drawing"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Right pixel indicator"
[node name="LeftToolIconCheckbox" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer"]
margin_top = 28.0
margin_right = 147.0
margin_bottom = 52.0
hint_tooltip = "Displays an icon of the selected left tool next to the cursor on the canvas"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
pressed = true
text = "Show left tool icon"
[node name="RightToolIconCheckbox" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer"]
margin_left = 151.0
margin_top = 28.0
margin_right = 306.0
margin_bottom = 52.0
hint_tooltip = "Displays an icon of the selected right tool next to the cursor on the canvas"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
pressed = true
text = "Show right tool icon"
[node name="HSeparator3" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_top = 92.0
margin_right = 306.0
margin_bottom = 96.0
[node name="PressureSentivity" type="HBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
visible = false
margin_top = 116.0
margin_right = 334.0
margin_bottom = 136.0
[node name="PressureSensitivityLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/PressureSentivity"]
margin_top = 3.0
margin_right = 173.0
margin_bottom = 17.0
text = "Tablet pressure sensitivity:"
[node name="PressureSensitivityOptionButton" type="OptionButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/PressureSentivity"]
margin_left = 177.0
margin_right = 334.0
margin_bottom = 20.0
text = "Affect Brush's Alpha"
items = [ "None", null, false, 0, null, "Affect Brush's Alpha", null, false, 1, null ]
selected = 1
[node name="OpenLastProject" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_top = 100.0
margin_right = 306.0
margin_bottom = 124.0
hint_tooltip = "Opens last opened project on startup"
mouse_default_cursor_shape = 2
pressed = true
text = "Open last project on startup"
[node name="EnableAutosave" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_top = 128.0
margin_right = 306.0
margin_bottom = 152.0
mouse_default_cursor_shape = 2
text = "Enable autosave"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AutosaveInterval" type="HBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"]
margin_top = 156.0
margin_right = 306.0
margin_bottom = 180.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AutosaveIntervalLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/AutosaveInterval"]
margin_top = 5.0
margin_right = 115.0
margin_bottom = 19.0
text = "Autosave interval:"
[node name="AutosaveInterval" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/AutosaveInterval"]
margin_left = 119.0
margin_right = 306.0
margin_bottom = 24.0
size_flags_horizontal = 3
min_value = 1.0
max_value = 30.0
value = 1.0
align = 2
suffix = "minute(s)"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Languages" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"]
margin_top = 184.0
margin_right = 306.0
margin_bottom = 576.0
[node name="System Language" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_right = 306.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
pressed = true
text = "System Language"
[node name="German" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 28.0
margin_right = 306.0
margin_bottom = 52.0
mouse_default_cursor_shape = 2
text = "Deutsch [de]"
[node name="Greek" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 56.0
margin_right = 306.0
margin_bottom = 80.0
mouse_default_cursor_shape = 2
custom_fonts/font = ExtResource( 2 )
text = "Ελληνικά [el]"
[node name="English" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 84.0
margin_right = 306.0
margin_bottom = 108.0
mouse_default_cursor_shape = 2
text = "English [en]"
[node name="Esperanto" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 112.0
margin_right = 306.0
margin_bottom = 136.0
mouse_default_cursor_shape = 2
text = "Esperanto [eo]"
[node name="Spanish" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 140.0
margin_right = 306.0
margin_bottom = 164.0
mouse_default_cursor_shape = 2
text = "Español [es]"
[node name="French" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 168.0
margin_right = 306.0
margin_bottom = 192.0
mouse_default_cursor_shape = 2
text = "Français [fr]"
[node name="Italian" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 196.0
margin_right = 306.0
margin_bottom = 220.0
mouse_default_cursor_shape = 2
text = "Italiano [it]"
[node name="Latvian" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 224.0
margin_right = 306.0
margin_bottom = 248.0
mouse_default_cursor_shape = 2
text = "Latvian [lv]"
[node name="Polish" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 252.0
margin_right = 306.0
margin_bottom = 276.0
mouse_default_cursor_shape = 2
text = "Polski [pl]"
[node name="Brazilian Portuguese" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 280.0
margin_right = 306.0
margin_bottom = 304.0
mouse_default_cursor_shape = 2
text = "Português Brasileiro [pt_BR]"
[node name="Russian" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 308.0
margin_right = 306.0
margin_bottom = 332.0
mouse_default_cursor_shape = 2
text = "Русский [ru]"
[node name="Chinese Simplified" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 336.0
margin_right = 306.0
margin_bottom = 362.0
mouse_default_cursor_shape = 2
custom_fonts/font = ExtResource( 3 )
text = "简体中文 [zh_CN]"
[node name="Chinese Traditional" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"]
margin_top = 366.0
margin_right = 306.0
margin_bottom = 392.0
mouse_default_cursor_shape = 2
custom_fonts/font = ExtResource( 3 )
text = "繁體中文 [zh_TW]"
[node name="Themes" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"]
margin_top = 580.0
margin_right = 306.0
margin_bottom = 716.0
[node name="Dark Theme" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Themes"]
margin_right = 306.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
text = "Dark"
[node name="Gray Theme" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Themes"]
margin_top = 28.0
margin_right = 306.0
margin_bottom = 52.0
mouse_default_cursor_shape = 2
text = "Gray"
[node name="Godot\'s Theme" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Themes"]
margin_top = 56.0
margin_right = 306.0
margin_bottom = 80.0
mouse_default_cursor_shape = 2
text = "Godot"
[node name="Gold Theme" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Themes"]
margin_top = 84.0
margin_right = 306.0
margin_bottom = 108.0
mouse_default_cursor_shape = 2
text = "Gold"
[node name="Light Theme" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Themes"]
margin_top = 112.0
margin_right = 306.0
margin_bottom = 136.0
mouse_default_cursor_shape = 2
text = "Light"
[node name="Canvas" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"]
margin_top = 720.0
margin_right = 306.0
margin_bottom = 912.0
[node name="GuideOptions" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas"]
margin_right = 306.0
margin_bottom = 20.0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="GuideColorLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GuideOptions"]
margin_top = 3.0
margin_right = 110.0
margin_bottom = 17.0
rect_min_size = Vector2( 110, 0 )
hint_tooltip = "A color of ruler guides displayed on the canvas"
mouse_filter = 0
text = "Guides color:"
[node name="GuideColor" type="ColorPickerButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GuideOptions"]
margin_left = 114.0
margin_right = 306.0
margin_bottom = 20.0
rect_min_size = Vector2( 64, 20 )
hint_tooltip = "A color of ruler guides displayed on the canvas"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
color = Color( 0.63, 0.13, 0.94, 1 )
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas"]
margin_top = 24.0
margin_right = 306.0
margin_bottom = 28.0
[node name="GridOptions" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas"]
margin_top = 32.0
margin_right = 306.0
margin_bottom = 108.0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="WidthLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"]
margin_top = 5.0
margin_right = 110.0
margin_bottom = 19.0
rect_min_size = Vector2( 110, 0 )
hint_tooltip = "Sets how far apart are vertical lines of the grid"
mouse_filter = 0
text = "Grid width:"
[node name="GridWidthValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"]
margin_left = 114.0
margin_right = 306.0
margin_bottom = 24.0
hint_tooltip = "Sets how far apart are vertical lines of the grid"
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 1.0
align = 2
suffix = "px"
[node name="Height" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"]
margin_top = 33.0
margin_right = 110.0
margin_bottom = 47.0
hint_tooltip = "Sets how far apart are horizontal lines of the grid"
mouse_filter = 0
text = "Grid height:"
[node name="GridHeightValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"]
margin_left = 114.0
margin_top = 28.0
margin_right = 306.0
margin_bottom = 52.0
hint_tooltip = "Sets how far apart are horizontal lines of the grid"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 16384.0
value = 1.0
align = 2
suffix = "px"
[node name="GridColorLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"]
margin_top = 59.0
margin_right = 110.0
margin_bottom = 73.0
hint_tooltip = "A color of the grid"
mouse_filter = 0
text = "Grid color:"
[node name="GridColor" type="ColorPickerButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions"]
margin_left = 114.0
margin_top = 56.0
margin_right = 306.0
margin_bottom = 76.0
rect_min_size = Vector2( 64, 20 )
hint_tooltip = "A color of the grid"
mouse_default_cursor_shape = 2
[node name="HSeparator2" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas"]
margin_top = 112.0
margin_right = 306.0
margin_bottom = 116.0
[node name="CheckerOptions" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas"]
margin_top = 120.0
margin_right = 306.0
margin_bottom = 192.0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="SizeLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions"]
margin_top = 5.0
margin_right = 110.0
margin_bottom = 19.0
rect_min_size = Vector2( 110, 0 )
hint_tooltip = "Size of the transparent checker background"
mouse_filter = 0
text = "Checker size:"
[node name="CheckerSizeValue" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions"]
margin_left = 114.0
margin_right = 188.0
margin_bottom = 24.0
hint_tooltip = "Size of the transparent checker background"
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 10.0
align = 2
suffix = "px"
[node name="CheckerColor1Label" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions"]
margin_top = 31.0
margin_right = 110.0
margin_bottom = 45.0
hint_tooltip = "First color of the transparent checker background"
mouse_filter = 0
text = "Checker color 1:"
[node name="CheckerColor1" type="ColorPickerButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions"]
margin_left = 114.0
margin_top = 28.0
margin_right = 188.0
margin_bottom = 48.0
rect_min_size = Vector2( 64, 20 )
hint_tooltip = "First color of the transparent checker background"
mouse_default_cursor_shape = 2
color = Color( 0.7, 0.7, 0.7, 1 )
[node name="CheckerColor2Label" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions"]
margin_top = 55.0
margin_right = 110.0
margin_bottom = 69.0
hint_tooltip = "Second color of the transparent checker background"
mouse_filter = 0
text = "Checker color 2:"
[node name="CheckerColor2" type="ColorPickerButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions"]
margin_left = 114.0
margin_top = 52.0
margin_right = 188.0
margin_bottom = 72.0
rect_min_size = Vector2( 64, 20 )
hint_tooltip = "Second color of the transparent checker background"
mouse_default_cursor_shape = 2
color = Color( 1, 1, 1, 1 )
[node name="Image" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"]
margin_top = 916.0
margin_right = 306.0
margin_bottom = 992.0
[node name="ImageOptions" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image"]
margin_right = 306.0
margin_bottom = 76.0
custom_constants/vseparation = 4
custom_constants/hseparation = 4
columns = 2
[node name="DefaultWidthLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions"]
margin_top = 5.0
margin_right = 110.0
margin_bottom = 19.0
rect_min_size = Vector2( 110, 0 )
hint_tooltip = "A default width of a new image"
mouse_filter = 0
text = "Default width:"
[node name="ImageDefaultWidth" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions"]
margin_left = 114.0
margin_right = 306.0
margin_bottom = 24.0
hint_tooltip = "A default width of a new image"
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
min_value = 1.0
max_value = 16384.0
value = 64.0
align = 2
suffix = "px"
[node name="DefaultHeightLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions"]
margin_top = 33.0
margin_right = 110.0
margin_bottom = 47.0
hint_tooltip = "A default height of a new image"
mouse_filter = 0
text = "Default height:"
[node name="ImageDefaultHeight" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions"]
margin_left = 114.0
margin_top = 28.0
margin_right = 306.0
margin_bottom = 52.0
hint_tooltip = "A default height of a new image"
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 64.0
align = 2
suffix = "px"
[node name="DefaultFillColorLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions"]
margin_top = 59.0
margin_right = 110.0
margin_bottom = 73.0
hint_tooltip = "A default background color of a new image"
mouse_filter = 0
text = "Default fill color:"
[node name="DefaultFillColor" type="ColorPickerButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions"]
margin_left = 114.0
margin_top = 56.0
margin_right = 306.0
margin_bottom = 76.0
rect_min_size = Vector2( 64, 20 )
hint_tooltip = "A default background color of a new image"
mouse_default_cursor_shape = 2
color = Color( 0, 0, 0, 0 )
[node name="Shortcuts" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"]
margin_top = 996.0
margin_right = 306.0
margin_bottom = 1230.0
[node name="HBoxContainer" type="HBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts"]
margin_right = 306.0
margin_bottom = 20.0
hint_tooltip = "Only custom preset can be modified"
[node name="Label" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/HBoxContainer"]
margin_top = 3.0
margin_right = 45.0
margin_bottom = 17.0
text = "Preset:"
[node name="PresetOptionButton" type="OptionButton" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/HBoxContainer"]
margin_left = 49.0
margin_right = 306.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Default"
items = [ "Default", null, false, 0, null, "Custom", null, false, 1, null ]
selected = 0
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts"]
margin_top = 24.0
margin_right = 306.0
margin_bottom = 28.0
[node name="Shortcuts" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts"]
margin_top = 32.0
margin_right = 306.0
margin_bottom = 234.0
custom_constants/vseparation = 2
custom_constants/hseparation = 5
columns = 3
[node name="Empty" type="Control" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_right = 137.0
margin_bottom = 14.0
[node name="LeftToolLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_right = 221.0
margin_bottom = 14.0
hint_tooltip = "A tool assigned to the left mouse button"
mouse_filter = 0
text = "Left Tool:"
align = 1
[node name="RightToolLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_right = 305.0
margin_bottom = 14.0
hint_tooltip = "A tool assigned to the right mouse button"
mouse_filter = 0
text = "Right Tool:"
align = 1
[node name="Empty2" type="Control" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 16.0
margin_right = 137.0
margin_bottom = 20.0
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
visible = false
margin_top = 18.0
margin_right = 137.0
margin_bottom = 22.0
[node name="HSeparator2" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 16.0
margin_right = 221.0
margin_bottom = 20.0
[node name="HSeparator3" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 16.0
margin_right = 305.0
margin_bottom = 20.0
[node name="RectSelectLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 25.0
margin_right = 137.0
margin_bottom = 39.0
text = "Rectangular Selection"
[node name="left_rectangle_select_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 22.0
margin_right = 221.0
margin_bottom = 42.0
size_flags_horizontal = 3
[node name="right_rectangle_select_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 22.0
margin_right = 305.0
margin_bottom = 42.0
size_flags_horizontal = 3
[node name="ZoomLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 47.0
margin_right = 137.0
margin_bottom = 61.0
text = "Zoom"
[node name="left_zoom_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 44.0
margin_right = 221.0
margin_bottom = 64.0
size_flags_horizontal = 3
[node name="right_zoom_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 44.0
margin_right = 305.0
margin_bottom = 64.0
size_flags_horizontal = 3
[node name="ColorPickerLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 69.0
margin_right = 137.0
margin_bottom = 83.0
text = "Color Picker"
[node name="left_colorpicker_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 66.0
margin_right = 221.0
margin_bottom = 86.0
[node name="right_colorpicker_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 66.0
margin_right = 305.0
margin_bottom = 86.0
[node name="PencilLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 91.0
margin_right = 137.0
margin_bottom = 105.0
text = "Pencil"
[node name="left_pencil_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 88.0
margin_right = 221.0
margin_bottom = 108.0
[node name="right_pencil_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 88.0
margin_right = 305.0
margin_bottom = 108.0
[node name="EraserLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 113.0
margin_right = 137.0
margin_bottom = 127.0
text = "Eraser"
[node name="left_eraser_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 110.0
margin_right = 221.0
margin_bottom = 130.0
[node name="right_eraser_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 110.0
margin_right = 305.0
margin_bottom = 130.0
[node name="BucketLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 135.0
margin_right = 137.0
margin_bottom = 149.0
text = "Bucket"
[node name="left_fill_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 132.0
margin_right = 221.0
margin_bottom = 152.0
[node name="right_fill_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 132.0
margin_right = 305.0
margin_bottom = 152.0
[node name="LightenDarkenLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 157.0
margin_right = 137.0
margin_bottom = 171.0
text = "Lighten/Darken"
[node name="left_lightdark_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 154.0
margin_right = 221.0
margin_bottom = 174.0
[node name="right_lightdark_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 154.0
margin_right = 305.0
margin_bottom = 174.0
[node name="HSeparator4" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 176.0
margin_right = 137.0
margin_bottom = 180.0
[node name="HSeparator5" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 176.0
margin_right = 221.0
margin_bottom = 180.0
[node name="HSeparator6" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 226.0
margin_top = 176.0
margin_right = 305.0
margin_bottom = 180.0
[node name="Switch Colors" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_top = 185.0
margin_right = 137.0
margin_bottom = 199.0
text = "Switch Colors"
[node name="switch_colors" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"]
margin_left = 142.0
margin_top = 182.0
margin_right = 221.0
margin_bottom = 202.0
[node name="Popups" type="Node" parent="."]
[node name="ShortcutSelector" type="ConfirmationDialog" parent="Popups"]
margin_right = 250.0
margin_bottom = 87.5
rect_min_size = Vector2( 250, 87.5 )
window_title = "Set the shortcut"
dialog_text = "Press a key or a key combination to set the shortcut"
dialog_hide_on_ok = false
__meta__ = {
"_edit_use_anchors_": false
}
[node name="EnteredShortcut" type="Label" parent="Popups/ShortcutSelector"]
margin_left = 8.0
margin_top = 22.0
margin_right = 341.0
margin_bottom = 51.5
align = 1
valign = 1
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="about_to_show" from="." to="." method="_on_PreferencesDialog_about_to_show"]
[connection signal="popup_hide" from="." to="." method="_on_PreferencesDialog_popup_hide"]
[connection signal="item_selected" from="HSplitContainer/Tree" to="." method="_on_Tree_item_selected"]
[connection signal="pressed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/SmoothZoom" to="." method="_on_SmoothZoom_pressed"]
[connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/LeftIndicatorCheckbox" to="." method="_on_LeftIndicatorCheckbox_toggled"]
[connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/RightIndicatorCheckbox" to="." method="_on_RightIndicatorCheckbox_toggled"]
[connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/LeftToolIconCheckbox" to="." method="_on_LeftToolIconCheckbox_toggled"]
[connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/RightToolIconCheckbox" to="." method="_on_RightToolIconCheckbox_toggled"]
[connection signal="item_selected" from="HSplitContainer/ScrollContainer/VBoxContainer/General/PressureSentivity/PressureSensitivityOptionButton" to="." method="_on_PressureSensitivityOptionButton_item_selected"]
[connection signal="pressed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/OpenLastProject" to="." method="_on_OpenLastProject_pressed"]
[connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/EnableAutosave" to="." method="_on_EnableAutosave_toggled"]
[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/AutosaveInterval/AutosaveInterval" to="." method="_on_AutosaveInterval_value_changed"]
[connection signal="color_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GuideOptions/GuideColor" to="." method="_on_GuideColor_color_changed"]
[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridWidthValue" to="." method="_on_GridWidthValue_value_changed"]
[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridHeightValue" to="." method="_on_GridHeightValue_value_changed"]
[connection signal="color_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridColor" to="." method="_on_GridColor_color_changed"]
[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions/CheckerSizeValue" to="." method="_on_CheckerSize_value_changed"]
[connection signal="color_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions/CheckerColor1" to="." method="_on_CheckerColor1_color_changed"]
[connection signal="color_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/CheckerOptions/CheckerColor2" to="." method="_on_CheckerColor2_color_changed"]
[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions/ImageDefaultWidth" to="." method="_on_ImageDefaultWidth_value_changed"]
[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions/ImageDefaultHeight" to="." method="_on_ImageDefaultHeight_value_changed"]
[connection signal="color_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Image/ImageOptions/DefaultFillColor" to="." method="_on_DefaultBackground_color_changed"]
[connection signal="item_selected" from="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/HBoxContainer/PresetOptionButton" to="." method="_on_PresetOptionButton_item_selected"]
[connection signal="confirmed" from="Popups/ShortcutSelector" to="." method="_on_ShortcutSelector_confirmed"]
[connection signal="popup_hide" from="Popups/ShortcutSelector" to="." method="_on_ShortcutSelector_popup_hide"]

View file

@ -0,0 +1,61 @@
extends ConfirmationDialog
var texture : ImageTexture
var aux_img : Image
var layer : Image
func _ready() -> void:
texture = ImageTexture.new()
texture.flags = 0
aux_img = Image.new()
$VBoxContainer/HBoxContainer2/OptionButton.add_item("Rotxel")
$VBoxContainer/HBoxContainer2/OptionButton.add_item("Upscale, Rotate and Downscale")
$VBoxContainer/HBoxContainer2/OptionButton.add_item("Nearest neighbour")
func set_sprite(sprite : Image) -> void:
aux_img.copy_from(sprite)
layer = sprite
texture.create_from_image(aux_img, 0)
$VBoxContainer/TextureRect.texture = texture
func _on_HSlider_value_changed(_value) -> void:
rotate()
$VBoxContainer/HBoxContainer/SpinBox.value = $VBoxContainer/HBoxContainer/HSlider.value
func _on_SpinBox_value_changed(_value):
$VBoxContainer/HBoxContainer/HSlider.value = $VBoxContainer/HBoxContainer/SpinBox.value
func _on_RotateImage_confirmed() -> void:
Global.canvas.handle_undo("Draw")
match $VBoxContainer/HBoxContainer2/OptionButton.text:
"Rotxel":
Global.rotxel(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180)
"Nearest neighbour":
Global.nn_rotate(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180)
"Upscale, Rotate and Downscale":
Global.fake_rotsprite(layer,$VBoxContainer/HBoxContainer/HSlider.value*PI/180)
Global.canvas.handle_redo("Draw")
$VBoxContainer/HBoxContainer/HSlider.value = 0
func rotate() -> void:
var sprite : Image = Image.new()
sprite.copy_from(aux_img)
match $VBoxContainer/HBoxContainer2/OptionButton.text:
"Rotxel":
Global.rotxel(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180)
"Nearest neighbour":
Global.nn_rotate(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180)
"Upscale, Rotate and Downscale":
Global.fake_rotsprite(sprite,$VBoxContainer/HBoxContainer/HSlider.value*PI/180)
texture.create_from_image(sprite, 0)
func _on_OptionButton_item_selected(_id) -> void:
rotate()
func _on_RotateImage_about_to_show() -> void:
$VBoxContainer/HBoxContainer/HSlider.value = 0

View file

@ -0,0 +1,85 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Dialogs/RotateImage.gd" type="Script" id=1]
[node name="RotateImage" type="ConfirmationDialog"]
margin_right = 245.0
margin_bottom = 241.0
resizable = true
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 8.0
margin_top = 8.0
margin_right = -8.0
margin_bottom = -36.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TextureRect" type="TextureRect" parent="VBoxContainer"]
margin_right = 229.0
margin_bottom = 145.0
size_flags_vertical = 3
expand = true
stretch_mode = 6
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 149.0
margin_right = 229.0
margin_bottom = 169.0
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"]
margin_top = 3.0
margin_right = 34.0
margin_bottom = 17.0
text = "Type:"
[node name="OptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer2"]
margin_left = 38.0
margin_right = 229.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 173.0
margin_right = 229.0
margin_bottom = 197.0
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
margin_top = 5.0
margin_right = 44.0
margin_bottom = 19.0
text = "Angle: "
[node name="HSlider" type="HSlider" parent="VBoxContainer/HBoxContainer"]
margin_left = 48.0
margin_right = 151.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
size_flags_vertical = 3
max_value = 359.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="SpinBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
margin_left = 155.0
margin_right = 229.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
max_value = 359.0
[connection signal="about_to_show" from="." to="." method="_on_RotateImage_about_to_show"]
[connection signal="confirmed" from="." to="." method="_on_RotateImage_confirmed"]
[connection signal="item_selected" from="VBoxContainer/HBoxContainer2/OptionButton" to="." method="_on_OptionButton_item_selected"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/HSlider" to="." method="_on_HSlider_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/SpinBox" to="." method="_on_SpinBox_value_changed"]

21
src/Dialogs/ScaleImage.gd Normal file
View file

@ -0,0 +1,21 @@
extends ConfirmationDialog
func _on_ScaleImage_confirmed() -> void:
var width : int = $VBoxContainer/OptionsContainer/WidthValue.value
var height : int = $VBoxContainer/OptionsContainer/HeightValue.value
var interpolation : int = $VBoxContainer/OptionsContainer/InterpolationType.selected
Global.undos += 1
Global.undo_redo.create_action("Scale")
Global.undo_redo.add_do_property(Global.canvas, "size", Vector2(width, height).floor())
for i in range(Global.canvas.layers.size() - 1, -1, -1):
var sprite : Image = Global.canvas.layers[i][1].get_data()
sprite.resize(width, height, interpolation)
Global.undo_redo.add_do_property(Global.canvas.layers[i][0], "data", sprite.data)
Global.undo_redo.add_undo_property(Global.canvas.layers[i][0], "data", Global.canvas.layers[i][0].data)
Global.undo_redo.add_undo_property(Global.canvas, "size", Global.canvas.size)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvas])
Global.undo_redo.add_do_method(Global, "redo", [Global.canvas])
Global.undo_redo.commit_action()

View file

@ -0,0 +1,77 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/Dialogs/ScaleImage.gd" type="Script" id=1]
[node name="ScaleImage" type="ConfirmationDialog"]
margin_right = 200.0
margin_bottom = 114.0
script = ExtResource( 1 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 192.0
margin_bottom = 102.0
[node name="ImageSize" type="Label" parent="VBoxContainer"]
margin_right = 184.0
margin_bottom = 15.0
text = "Image Size"
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"]
margin_top = 19.0
margin_right = 184.0
margin_bottom = 90.0
custom_constants/vseparation = 4
custom_constants/hseparation = 2
columns = 2
[node name="WidthLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 5.0
margin_right = 72.0
margin_bottom = 20.0
text = "Width:"
[node name="WidthValue" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
margin_left = 72.0
margin_right = 155.0
margin_bottom = 25.0
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 64.0
suffix = "px"
[node name="Height" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 30.0
margin_right = 72.0
margin_bottom = 45.0
text = "Height:"
[node name="HeightValue" type="SpinBox" parent="VBoxContainer/OptionsContainer"]
margin_left = 72.0
margin_top = 25.0
margin_right = 155.0
margin_bottom = 50.0
mouse_default_cursor_shape = 2
min_value = 1.0
max_value = 16384.0
value = 64.0
suffix = "px"
[node name="InterpolationLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
margin_top = 53.0
margin_right = 72.0
margin_bottom = 68.0
text = "Interpolation:"
[node name="InterpolationType" type="OptionButton" parent="VBoxContainer/OptionsContainer"]
margin_left = 72.0
margin_top = 50.0
margin_right = 155.0
margin_bottom = 71.0
text = "Nearest"
items = [ "Nearest", null, false, 0, null, "Bilinear", null, false, 1, null, "Cubic", null, false, 2, null, "Trilinear", null, false, 3, null, "Lanczos", null, false, 4, null ]
selected = 0
[connection signal="confirmed" from="." to="." method="_on_ScaleImage_confirmed"]

View file

@ -0,0 +1,75 @@
extends WindowDialog
onready var changes_label : Label = $"Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Changlog/ChangesLabel"
onready var art_by_label : Label = $"Contents/HBoxContainer/Logo_ArtWork/CenterContainer/ArtContainer/ArtCredits"
onready var show_on_startup_button : CheckBox = $"Contents/MarginContainer/Info/VBoxContainer/HBoxContainer/ShowOnStartup"
onready var developed_by_label : Label = $"Contents/MarginContainer/Info/VBoxContainer/Branding/VBoxContainer/DevelopedBy"
onready var platinum_placeholder_label : Label = $"Contents/MarginContainer/Info/Sponsors/PlatinumContainer/PlaceholderLabel"
onready var gold_placeholder_label : Label = $"Contents/MarginContainer/Info/Sponsors/GoldContainer/PlaceholderLabel"
func _on_SplashDialog_about_to_show() -> void:
if Global.config_cache.has_section_key("preferences", "startup"):
show_on_startup_button.pressed = !Global.config_cache.get_value("preferences", "startup")
var current_version : String = ProjectSettings.get_setting("application/config/Version")
window_title = "Pixelorama" + " " + current_version
changes_label.text = current_version + " " + tr("Changes")
art_by_label.text = tr("Art by") + ": Erevos"
if "zh" in TranslationServer.get_locale():
show_on_startup_button.add_font_override("font", preload("res://Assets/Fonts/CJK/NotoSansCJKtc-Small.tres"))
developed_by_label.add_font_override("font", preload("res://Assets/Fonts/CJK/NotoSansCJKtc-Small.tres"))
platinum_placeholder_label.add_font_override("font", preload("res://Assets/Fonts/CJK/NotoSansCJKtc-Bold.tres"))
gold_placeholder_label.add_font_override("font", preload("res://Assets/Fonts/CJK/NotoSansCJKtc-Bold.tres"))
else:
show_on_startup_button.add_font_override("font", preload("res://Assets/Fonts/Roboto-Small.tres"))
developed_by_label.add_font_override("font", preload("res://Assets/Fonts/Roboto-Small.tres"))
platinum_placeholder_label.add_font_override("font", preload("res://Assets/Fonts/Roboto-Bold.tres"))
gold_placeholder_label.add_font_override("font", preload("res://Assets/Fonts/Roboto-Bold.tres"))
func _on_ArtCredits_pressed() -> void:
OS.shell_open("https://www.instagram.com/erevoid")
func _on_ShowOnStartup_toggled(pressed : bool) -> void:
if pressed:
Global.config_cache.set_value("preferences", "startup", false)
else:
Global.config_cache.set_value("preferences", "startup", true)
func _on_PatronButton_pressed() -> void:
OS.shell_open("https://www.patreon.com/OramaInteractive")
func _on_TakeThisSpot_pressed() -> void:
OS.shell_open("https://www.patreon.com/OramaInteractive")
func _on_GithubButton_pressed() -> void:
OS.shell_open("https://github.com/Orama-Interactive/Pixelorama")
func _on_DiscordButton_pressed() -> void:
OS.shell_open("https://discord.gg/GTMtr8s")
func _on_NewBtn_pressed() -> void:
Global.control.file_menu_id_pressed(0)
visible = false
func _on_OpenBtn__pressed() -> void:
Global.control.file_menu_id_pressed(1)
visible = false
func _on_OpenLastBtn_pressed() -> void:
Global.control.file_menu_id_pressed(2)
visible = false
func _on_ImportBtn_pressed() -> void:
Global.control.file_menu_id_pressed(5)
visible = false

View file

@ -0,0 +1,440 @@
[gd_scene load_steps=12 format=2]
[ext_resource path="res://src/Dialogs/SplashDialog.gd" type="Script" id=1]
[ext_resource path="res://Assets/Graphics/Pixelorama Logo.png" type="Texture" id=2]
[ext_resource path="res://Assets/Graphics/Become a patron.png" type="Texture" id=3]
[ext_resource path="res://Assets/Graphics/Become a patron_Hover.png" type="Texture" id=4]
[ext_resource path="res://Assets/Graphics/Splash Art.png" type="Texture" id=5]
[ext_resource path="res://Assets/Fonts/Roboto-Bold.tres" type="DynamicFont" id=6]
[ext_resource path="res://Assets/Fonts/Roboto-Small.tres" type="DynamicFont" id=7]
[ext_resource path="res://Assets/Graphics/orama_64x64.png" type="Texture" id=8]
[ext_resource path="res://Assets/Graphics/discord.png" type="Texture" id=9]
[ext_resource path="res://Assets/Graphics/GitHub-32px.png" type="Texture" id=10]
[ext_resource path="res://Assets/Graphics/Patreon_Mark_White.png" type="Texture" id=11]
[node name="SplashDialog" type="WindowDialog"]
margin_right = 614.0
margin_bottom = 590.0
rect_min_size = Vector2( 600, 560 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Contents" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
custom_constants/separation = 0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HBoxContainer" type="HBoxContainer" parent="Contents"]
margin_right = 617.0
margin_bottom = 436.0
custom_constants/separation = 0
[node name="Logo_ArtWork" type="VBoxContainer" parent="Contents/HBoxContainer"]
margin_right = 350.0
margin_bottom = 436.0
rect_min_size = Vector2( 350, 0 )
custom_constants/separation = 15
[node name="PixeloramaLogo" type="TextureRect" parent="Contents/HBoxContainer/Logo_ArtWork"]
margin_right = 350.0
margin_bottom = 124.0
rect_min_size = Vector2( 0, 80 )
size_flags_horizontal = 3
size_flags_vertical = 3
texture = ExtResource( 2 )
stretch_mode = 6
[node name="CenterContainer" type="MarginContainer" parent="Contents/HBoxContainer/Logo_ArtWork"]
margin_top = 139.0
margin_right = 350.0
margin_bottom = 436.0
size_flags_horizontal = 3
size_flags_vertical = 0
[node name="ArtContainer" type="VBoxContainer" parent="Contents/HBoxContainer/Logo_ArtWork/CenterContainer"]
margin_right = 350.0
margin_bottom = 297.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/separation = 17
[node name="SplashArt" type="TextureButton" parent="Contents/HBoxContainer/Logo_ArtWork/CenterContainer/ArtContainer"]
margin_left = 45.0
margin_right = 305.0
margin_bottom = 260.0
rect_min_size = Vector2( 260, 260 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 6
texture_normal = ExtResource( 5 )
expand = true
stretch_mode = 5
[node name="ArtCredits" type="Button" parent="Contents/HBoxContainer/Logo_ArtWork/CenterContainer/ArtContainer"]
margin_top = 277.0
margin_right = 350.0
margin_bottom = 297.0
mouse_default_cursor_shape = 2
custom_constants/hseparation = 0
text = "Art by Erevoid"
flat = true
[node name="VSeparator" type="VSeparator" parent="Contents/HBoxContainer"]
margin_left = 350.0
margin_right = 354.0
margin_bottom = 436.0
[node name="Buttons_Changelog" type="MarginContainer" parent="Contents/HBoxContainer"]
margin_left = 354.0
margin_right = 617.0
margin_bottom = 436.0
size_flags_horizontal = 3
custom_constants/margin_right = 10
custom_constants/margin_top = 10
custom_constants/margin_left = 10
custom_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="Contents/HBoxContainer/Buttons_Changelog"]
margin_left = 10.0
margin_top = 10.0
margin_right = 253.0
margin_bottom = 426.0
size_flags_horizontal = 3
custom_constants/separation = 10
[node name="Buttons" type="VBoxContainer" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer"]
margin_right = 243.0
margin_bottom = 110.0
size_flags_vertical = 3
custom_constants/separation = 10
[node name="NewBtn" type="Button" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons"]
margin_right = 243.0
margin_bottom = 20.0
text = "New"
[node name="OpenBtn " type="Button" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons"]
margin_top = 30.0
margin_right = 243.0
margin_bottom = 50.0
text = "Open"
[node name="OpenLastBtn" type="Button" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons"]
margin_top = 60.0
margin_right = 243.0
margin_bottom = 80.0
text = "Open Last Project"
[node name="ImportBtn" type="Button" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons"]
margin_top = 90.0
margin_right = 243.0
margin_bottom = 110.0
text = "Import"
[node name="HSeparator" type="HSeparator" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer"]
margin_top = 120.0
margin_right = 243.0
margin_bottom = 124.0
[node name="Changlog" type="VBoxContainer" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer"]
margin_top = 134.0
margin_right = 243.0
margin_bottom = 416.0
size_flags_vertical = 3
custom_constants/separation = 8
[node name="ChangesLabel" type="Label" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Changlog"]
margin_right = 243.0
margin_bottom = 14.0
size_flags_horizontal = 3
size_flags_vertical = 8
text = "v0.6 Changes"
[node name="ChangelogContainer" type="VBoxContainer" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Changlog"]
margin_top = 22.0
margin_right = 243.0
margin_bottom = 282.0
size_flags_horizontal = 3
[node name="ChangelogScroll" type="ScrollContainer" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Changlog/ChangelogContainer"]
margin_right = 243.0
margin_bottom = 260.0
rect_min_size = Vector2( 0, 260 )
[node name="Label" type="Label" parent="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Changlog/ChangelogContainer/ChangelogScroll"]
margin_right = 243.0
margin_bottom = 1680.0
size_flags_horizontal = 3
size_flags_vertical = 3
text = "Added
- Image layer rotation! Choose between 2 rotation algorithms, Rotxel and Nearest Neighbour - Thanks to azagaya!
- Crowdin integration for contributing translations!
- Spanish translation - thanks to azagaya & Lilly And!
- Chinese Simplified translation - thanks to Chenxu Wang!
- Latvian translation - thanks to Agnis Aldiņš (NeZvers)!
- Translators can now be seen in the About window.
- It is now possible to remove custom brushes with the middle mouse button.
- Added HSV mode to the color picker. (Added automatically because of the Godot 3.2 update)
- Lanczos scaling interpolation. (Added because of the Godot 3.2 update)
- You can now drag and drop (or right click and open with) image and .pxo files in Pixelorama.
- You can now hide the animation timeline - Thanks to YeldhamDev!
Changed
- Major changes to alpha blending behavior. The alpha values now get added/blended together instead of just replacing the pixel with the new value.
- Replaced some OS alerts with a custom made error dialog.
- Made the zooming smoother, is toggleable in Preferences whether to keep the new zooming or the old one.
- The camera now zooms at the mouse's position.
- Made the \"X\" button on the custom brushes a little smaller.
- The color picker will now have a small white triangle on the top left of the color preview if at least one of its RGB values are above 1 in Raw mode. (Added automatically because of the Godot 3.2 update)
- You can now toggle the visibility of hidden items on and off in the file dialogs. (Added automatically because of the Godot 3.2 update)
- The language buttons in the preferences have their localized names in their hint tooltips. For example, if you hover over the \"English\" button while the language is Greek, the hint tooltip will be \"Αγγλικά\", which is the Greek word for English.
- Translation updates.
- The presets in the ColorPickers are now hidden - Thanks to YeldhamDev!
- When opening a project (.pxo file), the save path is being set to the opened project's path - Thanks to YeldhamDev!
Fixed
- Delay the splash screen popup so it shows properly centered - Thanks to YeldhamDev!
- Possibly fixed crashes with motion drawing and undo/redoing.
- Fixed bug (which also caused crashes sometimes) when generating an outline inside the image and it was going outside the canvas' borders.
- Fixed crash when importing images that were failing to load. They still fail to load, but Pixelorama does not crash.
- Possibly fixed a rare crash where the cursor image was failing to load. It is now being loaded only once.
- Fixed ruler markings cutting off before they should - Thanks to YeldhamDev!
- Fixed bug where resizing the image on export was not working on Godot 3.2 - Issue #161"
autowrap = true
[node name="HSeparator" type="HSeparator" parent="Contents"]
margin_top = 436.0
margin_right = 617.0
margin_bottom = 440.0
[node name="MarginContainer" type="MarginContainer" parent="Contents"]
margin_top = 440.0
margin_right = 617.0
margin_bottom = 593.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/margin_right = 10
custom_constants/margin_top = 10
custom_constants/margin_left = 10
custom_constants/margin_bottom = 10
[node name="Info" type="HBoxContainer" parent="Contents/MarginContainer"]
margin_left = 10.0
margin_top = 10.0
margin_right = 607.0
margin_bottom = 143.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/separation = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Sponsors" type="HBoxContainer" parent="Contents/MarginContainer/Info"]
margin_right = 362.0
margin_bottom = 133.0
size_flags_vertical = 3
custom_constants/separation = 5
[node name="PlatinumContainer" type="VBoxContainer" parent="Contents/MarginContainer/Info/Sponsors"]
margin_right = 125.0
margin_bottom = 133.0
rect_min_size = Vector2( 125, 0 )
[node name="Label" type="Label" parent="Contents/MarginContainer/Info/Sponsors/PlatinumContainer"]
margin_right = 125.0
margin_bottom = 14.0
text = "Platinum Sponsor"
align = 1
[node name="PlaceholderLabel" type="Label" parent="Contents/MarginContainer/Info/Sponsors/PlatinumContainer"]
margin_top = 47.0
margin_right = 125.0
margin_bottom = 80.0
size_flags_vertical = 6
custom_fonts/font = ExtResource( 6 )
custom_colors/font_color = Color( 0.678431, 0.611765, 0.807843, 1 )
text = "Platinum
Sponsor"
align = 1
[node name="TakeThisSpot" type="Button" parent="Contents/MarginContainer/Info/Sponsors/PlatinumContainer"]
margin_top = 113.0
margin_right = 125.0
margin_bottom = 133.0
mouse_default_cursor_shape = 2
text = "Take this spot!"
flat = true
[node name="VSeparator" type="VSeparator" parent="Contents/MarginContainer/Info/Sponsors"]
margin_left = 130.0
margin_right = 134.0
margin_bottom = 133.0
[node name="GoldContainer" type="VBoxContainer" parent="Contents/MarginContainer/Info/Sponsors"]
margin_left = 139.0
margin_right = 264.0
margin_bottom = 133.0
rect_min_size = Vector2( 125, 0 )
size_flags_horizontal = 3
[node name="Label" type="Label" parent="Contents/MarginContainer/Info/Sponsors/GoldContainer"]
margin_right = 125.0
margin_bottom = 14.0
text = "Gold Sponsors"
align = 1
[node name="PlaceholderLabel" type="Label" parent="Contents/MarginContainer/Info/Sponsors/GoldContainer"]
margin_top = 47.0
margin_right = 125.0
margin_bottom = 80.0
size_flags_vertical = 6
custom_fonts/font = ExtResource( 6 )
custom_colors/font_color = Color( 0.678431, 0.611765, 0.807843, 1 )
text = "Gold
Sponsors"
align = 1
[node name="TakeThisSpot" type="Button" parent="Contents/MarginContainer/Info/Sponsors/GoldContainer"]
margin_top = 113.0
margin_right = 125.0
margin_bottom = 133.0
mouse_default_cursor_shape = 2
text = "Take this spot!"
flat = true
[node name="VSeparator2" type="VSeparator" parent="Contents/MarginContainer/Info/Sponsors"]
margin_left = 269.0
margin_right = 273.0
margin_bottom = 133.0
[node name="PatronContainer" type="VBoxContainer" parent="Contents/MarginContainer/Info/Sponsors"]
margin_left = 278.0
margin_right = 362.0
margin_bottom = 133.0
size_flags_horizontal = 3
custom_constants/separation = 10
alignment = 1
[node name="PatronsLabel" type="Label" parent="Contents/MarginContainer/Info/Sponsors/PatronContainer"]
margin_right = 84.0
margin_bottom = 14.0
size_flags_horizontal = 3
size_flags_vertical = 8
text = "Patrons:"
align = 1
[node name="PatronButton" type="TextureButton" parent="Contents/MarginContainer/Info/Sponsors/PatronContainer"]
margin_top = 24.0
margin_right = 84.0
margin_bottom = 133.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
size_flags_vertical = 3
texture_normal = ExtResource( 3 )
texture_hover = ExtResource( 4 )
stretch_mode = 5
[node name="VBoxContainer" type="VBoxContainer" parent="Contents/MarginContainer/Info"]
margin_left = 365.0
margin_right = 597.0
margin_bottom = 133.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/separation = 5
alignment = 2
[node name="Branding" type="HBoxContainer" parent="Contents/MarginContainer/Info/VBoxContainer"]
margin_right = 232.0
margin_bottom = 104.0
size_flags_horizontal = 3
size_flags_vertical = 3
alignment = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Contents/MarginContainer/Info/VBoxContainer/Branding"]
margin_left = 99.0
margin_right = 196.0
margin_bottom = 104.0
size_flags_vertical = 3
alignment = 1
[node name="Logo" type="TextureRect" parent="Contents/MarginContainer/Info/VBoxContainer/Branding/VBoxContainer"]
margin_top = 3.0
margin_right = 97.0
margin_bottom = 67.0
texture = ExtResource( 8 )
stretch_mode = 4
[node name="DevelopedBy" type="Label" parent="Contents/MarginContainer/Info/VBoxContainer/Branding/VBoxContainer"]
margin_top = 71.0
margin_right = 97.0
margin_bottom = 84.0
custom_fonts/font = ExtResource( 7 )
text = "Orama Interactive"
align = 1
[node name="Copyright" type="Label" parent="Contents/MarginContainer/Info/VBoxContainer/Branding/VBoxContainer"]
margin_top = 88.0
margin_right = 97.0
margin_bottom = 101.0
custom_fonts/font = ExtResource( 7 )
text = "Copyright 2019-2020"
align = 1
[node name="Links" type="VBoxContainer" parent="Contents/MarginContainer/Info/VBoxContainer/Branding"]
margin_left = 200.0
margin_right = 232.0
margin_bottom = 104.0
[node name="TextureButton" type="TextureButton" parent="Contents/MarginContainer/Info/VBoxContainer/Branding/Links"]
margin_right = 32.0
margin_bottom = 32.0
texture_normal = ExtResource( 10 )
[node name="TextureButton2" type="TextureButton" parent="Contents/MarginContainer/Info/VBoxContainer/Branding/Links"]
margin_top = 36.0
margin_right = 32.0
margin_bottom = 68.0
texture_normal = ExtResource( 9 )
[node name="TextureButton3" type="TextureButton" parent="Contents/MarginContainer/Info/VBoxContainer/Branding/Links"]
margin_top = 72.0
margin_right = 32.0
margin_bottom = 104.0
texture_normal = ExtResource( 11 )
[node name="HBoxContainer" type="HBoxContainer" parent="Contents/MarginContainer/Info/VBoxContainer"]
margin_top = 109.0
margin_right = 232.0
margin_bottom = 133.0
alignment = 2
[node name="ShowOnStartup" type="CheckBox" parent="Contents/MarginContainer/Info/VBoxContainer/HBoxContainer"]
margin_left = 127.0
margin_right = 232.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
custom_fonts/font = ExtResource( 7 )
text = "Don't show again"
[connection signal="about_to_show" from="." to="." method="_on_SplashDialog_about_to_show"]
[connection signal="pressed" from="Contents/HBoxContainer/Logo_ArtWork/CenterContainer/ArtContainer/SplashArt" to="." method="_on_ArtCredits_pressed"]
[connection signal="pressed" from="Contents/HBoxContainer/Logo_ArtWork/CenterContainer/ArtContainer/ArtCredits" to="." method="_on_ArtCredits_pressed"]
[connection signal="pressed" from="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons/NewBtn" to="." method="_on_NewBtn_pressed"]
[connection signal="pressed" from="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons/OpenBtn " to="." method="_on_OpenBtn__pressed"]
[connection signal="pressed" from="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons/OpenLastBtn" to="." method="_on_OpenLastBtn_pressed"]
[connection signal="pressed" from="Contents/HBoxContainer/Buttons_Changelog/VBoxContainer/Buttons/ImportBtn" to="." method="_on_ImportBtn_pressed"]
[connection signal="pressed" from="Contents/MarginContainer/Info/Sponsors/PlatinumContainer/TakeThisSpot" to="." method="_on_TakeThisSpot_pressed"]
[connection signal="pressed" from="Contents/MarginContainer/Info/Sponsors/GoldContainer/TakeThisSpot" to="." method="_on_TakeThisSpot_pressed"]
[connection signal="pressed" from="Contents/MarginContainer/Info/Sponsors/PatronContainer/PatronButton" to="." method="_on_PatronButton_pressed"]
[connection signal="pressed" from="Contents/MarginContainer/Info/VBoxContainer/Branding/Links/TextureButton" to="." method="_on_GithubButton_pressed"]
[connection signal="pressed" from="Contents/MarginContainer/Info/VBoxContainer/Branding/Links/TextureButton2" to="." method="_on_DiscordButton_pressed"]
[connection signal="pressed" from="Contents/MarginContainer/Info/VBoxContainer/Branding/Links/TextureButton3" to="." method="_on_PatronButton_pressed"]
[connection signal="toggled" from="Contents/MarginContainer/Info/VBoxContainer/HBoxContainer/ShowOnStartup" to="." method="_on_ShowOnStartup_toggled"]

40
src/Drawers.gd Normal file
View file

@ -0,0 +1,40 @@
class Drawer:
func reset() -> void:
pass
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
pass
class SimpleDrawer extends Drawer:
func reset() -> void:
pass
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
_sprite.set_pixel(_pos.x, _pos.y, _new_color)
class PixelPerfectDrawer extends Drawer:
const neighbours = [Vector2(0, 1), Vector2(1, 0), Vector2(-1, 0), Vector2(0, -1)]
const corners = [Vector2(1, 1), Vector2(-1, -1), Vector2(-1, 1), Vector2(1, -1)]
var last_pixels = [null, null]
func reset():
last_pixels = [null, null]
func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void:
last_pixels.push_back([_pos, _sprite.get_pixel(_pos.x, _pos.y)])
_sprite.set_pixel(_pos.x, _pos.y, _new_color)
var corner = last_pixels.pop_front()
var neighbour = last_pixels[0]
if corner == null or neighbour == null:
return
if _pos - corner[0] in corners and _pos - neighbour[0] in neighbours:
_sprite.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1])
last_pixels[0] = corner

80
src/LayerButton.gd Normal file
View file

@ -0,0 +1,80 @@
class_name LayerButton
extends Button
var i := 0
var visibility_button : BaseButton
var lock_button : BaseButton
var linked_button : BaseButton
var label : Label
var line_edit : LineEdit
func _ready() -> void:
visibility_button = Global.find_node_by_name(self, "VisibilityButton")
lock_button = Global.find_node_by_name(self, "LockButton")
linked_button = Global.find_node_by_name(self, "LinkButton")
label = Global.find_node_by_name(self, "Label")
line_edit = Global.find_node_by_name(self, "LineEdit")
if Global.layers[i][1]:
visibility_button.texture_normal = load("res://Assets/Graphics/%s Themes/Layers/Layer_Visible.png" % Global.theme_type)
visibility_button.texture_hover = load("res://Assets/Graphics/%s Themes/Layers/Layer_Visible_Hover.png" % Global.theme_type)
else:
visibility_button.texture_normal = load("res://Assets/Graphics/%s Themes/Layers/Layer_Invisible.png" % Global.theme_type)
visibility_button.texture_hover = load("res://Assets/Graphics/%s Themes/Layers/Layer_Invisible_Hover.png" % Global.theme_type)
if Global.layers[i][2]:
lock_button.texture_normal = load("res://Assets/Graphics/%s Themes/Layers/Lock.png" % Global.theme_type)
lock_button.texture_hover = load("res://Assets/Graphics/%s Themes/Layers/Lock_Hover.png" % Global.theme_type)
else:
lock_button.texture_normal = load("res://Assets/Graphics/%s Themes/Layers/Unlock.png" % Global.theme_type)
lock_button.texture_hover = load("res://Assets/Graphics/%s Themes/Layers/Unlock_Hover.png" % Global.theme_type)
if Global.layers[i][4]: # If new layers will be linked
linked_button.texture_normal = load("res://Assets/Graphics/%s Themes/Layers/Linked_Layer.png" % Global.theme_type)
linked_button.texture_hover = load("res://Assets/Graphics/%s Themes/Layers/Linked_Layer_Hover.png" % Global.theme_type)
else:
linked_button.texture_normal = load("res://Assets/Graphics/%s Themes/Layers/Unlinked_Layer.png" % Global.theme_type)
linked_button.texture_hover = load("res://Assets/Graphics/%s Themes/Layers/Unlinked_Layer_Hover.png" % Global.theme_type)
func _input(event : InputEvent) -> void:
if (event.is_action_released("ui_accept") or event.is_action_released("ui_cancel")) and line_edit.visible and event.scancode != KEY_SPACE:
save_layer_name(line_edit.text)
func _on_LayerContainer_pressed() -> void:
pressed = !pressed
label.visible = false
line_edit.visible = true
line_edit.editable = true
line_edit.grab_focus()
func _on_LineEdit_focus_exited() -> void:
save_layer_name(line_edit.text)
func save_layer_name(new_name : String) -> void:
label.visible = true
line_edit.visible = false
line_edit.editable = false
label.text = new_name
Global.layers_changed_skip = true
Global.layers[i][0] = new_name
func _on_VisibilityButton_pressed() -> void:
Global.layers[i][1] = !Global.layers[i][1]
Global.canvas.update()
func _on_LockButton_pressed() -> void:
Global.layers[i][2] = !Global.layers[i][2]
func _on_LinkButton_pressed() -> void:
Global.layers[i][4] = !Global.layers[i][4]
if Global.layers[i][4] && !Global.layers[i][5]:
Global.layers[i][5].append(Global.canvas)
Global.layers[i][3].get_child(Global.current_frame)._ready()

119
src/LayerButton.tscn Normal file
View file

@ -0,0 +1,119 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Unlinked_Layer.png" type="Texture" id=1]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Unlock_Hover.png" type="Texture" id=2]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Unlock.png" type="Texture" id=3]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Unlinked_Layer_Hover.png" type="Texture" id=4]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Layer_Visible.png" type="Texture" id=5]
[ext_resource path="res://Assets/Graphics/Dark Themes/Layers/Layer_Visible_Hover.png" type="Texture" id=6]
[ext_resource path="res://src/LayerButton.gd" type="Script" id=7]
[node name="LayerContainer" type="Button"]
margin_right = 210.0
margin_bottom = 36.0
rect_min_size = Vector2( 212, 36 )
size_flags_horizontal = 0
toggle_mode = true
action_mode = 0
script = ExtResource( 7 )
__meta__ = {
"_edit_horizontal_guides_": [ ],
"_edit_use_anchors_": false
}
[node name="HBoxContainer" type="HBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LayerButtons" type="HBoxContainer" parent="HBoxContainer"]
margin_right = 104.0
margin_bottom = 36.0
[node name="VisibilityButton" type="TextureButton" parent="HBoxContainer/LayerButtons" groups=[
"UIButtons",
]]
margin_top = 2.0
margin_right = 32.0
margin_bottom = 34.0
hint_tooltip = "Toggle layer's visibility"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 4
texture_normal = ExtResource( 5 )
texture_hover = ExtResource( 6 )
[node name="LockButton" type="TextureButton" parent="HBoxContainer/LayerButtons" groups=[
"UIButtons",
]]
margin_left = 36.0
margin_top = 2.0
margin_right = 68.0
margin_bottom = 34.0
hint_tooltip = "Lock/unlock layer"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 4
texture_normal = ExtResource( 3 )
texture_hover = ExtResource( 2 )
[node name="LinkButton" type="TextureButton" parent="HBoxContainer/LayerButtons" groups=[
"UIButtons",
]]
margin_left = 72.0
margin_top = 2.0
margin_right = 104.0
margin_bottom = 34.0
hint_tooltip = "Enable/disable cel linking
Linked cels are being shared across multiple frames"
focus_mode = 0
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
size_flags_vertical = 4
texture_normal = ExtResource( 1 )
texture_hover = ExtResource( 4 )
[node name="LayerName" type="HBoxContainer" parent="HBoxContainer"]
margin_left = 108.0
margin_right = 212.0
margin_bottom = 36.0
rect_min_size = Vector2( 104, 0 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 0
alignment = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Label" type="Label" parent="HBoxContainer/LayerName"]
margin_top = 11.0
margin_right = 104.0
margin_bottom = 25.0
size_flags_horizontal = 3
text = "Layer 0"
align = 1
clip_text = true
[node name="LineEdit" type="LineEdit" parent="HBoxContainer/LayerName"]
visible = false
margin_left = 86.0
margin_top = 5.0
margin_right = 166.0
margin_bottom = 37.0
rect_min_size = Vector2( 80, 32 )
size_flags_vertical = 4
text = "Layer 0"
editable = false
caret_blink = true
caret_blink_speed = 0.5
[connection signal="pressed" from="." to="." method="_on_LayerContainer_pressed"]
[connection signal="pressed" from="HBoxContainer/LayerButtons/VisibilityButton" to="." method="_on_VisibilityButton_pressed"]
[connection signal="pressed" from="HBoxContainer/LayerButtons/LockButton" to="." method="_on_LockButton_pressed"]
[connection signal="pressed" from="HBoxContainer/LayerButtons/LinkButton" to="." method="_on_LinkButton_pressed"]
[connection signal="focus_exited" from="HBoxContainer/LayerName/LineEdit" to="." method="_on_LineEdit_focus_exited"]

869
src/Main.gd Normal file
View file

@ -0,0 +1,869 @@
extends Control
var opensprite_file_selected := false
var file_menu : PopupMenu
var view_menu : PopupMenu
var tools := []
var redone := false
var unsaved_canvas_state := 0
var is_quitting_on_save := false
var previous_left_color := Color.black
var previous_right_color := Color.white
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
get_tree().set_auto_accept_quit(false)
# Set a minimum window size to prevent UI elements from collapsing on each other.
# This property is only available in 3.2alpha or later, so use `set()` to fail gracefully if it doesn't exist.
OS.set("min_window_size", Vector2(1024, 576))
# `TranslationServer.get_loaded_locales()` was added in 3.2beta and in 3.1.2
# The `has_method()` check and the `else` branch can be removed once 3.2 is released.
if TranslationServer.has_method("get_loaded_locales"):
Global.loaded_locales = TranslationServer.get_loaded_locales()
else:
# Hardcoded list of locales
Global.loaded_locales = ["de_DE", "el_GR", "en_US", "eo_UY", "es_ES", "fr_FR", "it_IT", "lv_LV", "pl_PL", "pt_BR", "ru_RU", "zh_CN","zh_TW"]
# Make sure locales are always sorted, in the same order
Global.loaded_locales.sort()
# Restore the window position/size if values are present in the configuration cache
if Global.config_cache.has_section_key("window", "screen"):
OS.current_screen = Global.config_cache.get_value("window", "screen")
if Global.config_cache.has_section_key("window", "maximized"):
OS.window_maximized = Global.config_cache.get_value("window", "maximized")
if !OS.window_maximized:
if Global.config_cache.has_section_key("window", "position"):
OS.window_position = Global.config_cache.get_value("window", "position")
if Global.config_cache.has_section_key("window", "size"):
OS.window_size = Global.config_cache.get_value("window", "size")
var file_menu_items := {
"New..." : InputMap.get_action_list("new_file")[0].get_scancode_with_modifiers(),
"Open..." : InputMap.get_action_list("open_file")[0].get_scancode_with_modifiers(),
'Open last project...' : 0,
"Save..." : InputMap.get_action_list("save_file")[0].get_scancode_with_modifiers(),
"Save as..." : InputMap.get_action_list("save_file_as")[0].get_scancode_with_modifiers(),
"Import..." : InputMap.get_action_list("import_file")[0].get_scancode_with_modifiers(),
"Export..." : InputMap.get_action_list("export_file")[0].get_scancode_with_modifiers(),
"Export as..." : InputMap.get_action_list("export_file_as")[0].get_scancode_with_modifiers(),
"Quit" : InputMap.get_action_list("quit")[0].get_scancode_with_modifiers(),
}
var edit_menu_items := {
"Undo" : InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(),
"Redo" : InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(),
"Clear Selection" : 0,
"Preferences" : 0
}
var view_menu_items := {
"Tile Mode" : InputMap.get_action_list("tile_mode")[0].get_scancode_with_modifiers(),
"Show Grid" : InputMap.get_action_list("show_grid")[0].get_scancode_with_modifiers(),
"Show Rulers" : InputMap.get_action_list("show_rulers")[0].get_scancode_with_modifiers(),
"Show Guides" : InputMap.get_action_list("show_guides")[0].get_scancode_with_modifiers(),
"Show Animation Timeline" : 0
}
var image_menu_items := {
"Scale Image" : 0,
"Crop Image" : 0,
"Flip Horizontal" : InputMap.get_action_list("image_flip_horizontal")[0].get_scancode_with_modifiers(),
"Flip Vertical" : InputMap.get_action_list("image_flip_vertical")[0].get_scancode_with_modifiers(),
"Rotate Image" : 0,
"Invert colors" : 0,
"Desaturation" : 0,
"Outline" : 0,
"Adjust Hue/Saturation/Value" : 0
}
var help_menu_items := {
"View Splash Screen" : 0,
"Issue Tracker" : 0,
"Changelog" : 0,
"About Pixelorama" : 0
}
# Load language
if Global.config_cache.has_section_key("preferences", "locale"):
var saved_locale : String = Global.config_cache.get_value("preferences", "locale")
TranslationServer.set_locale(saved_locale)
# Set the language option menu's default selected option to the loaded locale
var locale_index: int = Global.loaded_locales.find(saved_locale)
$PreferencesDialog.languages.get_child(0).pressed = false # Unset System Language option in preferences
$PreferencesDialog.languages.get_child(locale_index + 1).pressed = true
else: # If the user doesn't have a language preference, set it to their OS' locale
TranslationServer.set_locale(OS.get_locale())
if "zh" in TranslationServer.get_locale():
theme.default_font = preload("res://Assets/Fonts/CJK/NotoSansCJKtc-Regular.tres")
else:
theme.default_font = preload("res://Assets/Fonts/Roboto-Regular.tres")
file_menu = Global.file_menu.get_popup()
var edit_menu : PopupMenu = Global.edit_menu.get_popup()
view_menu = Global.view_menu.get_popup()
var image_menu : PopupMenu = Global.image_menu.get_popup()
var help_menu : PopupMenu = Global.help_menu.get_popup()
var i = 0
for item in file_menu_items.keys():
file_menu.add_item(item, i, file_menu_items[item])
i += 1
i = 0
for item in edit_menu_items.keys():
edit_menu.add_item(item, i, edit_menu_items[item])
i += 1
i = 0
for item in view_menu_items.keys():
view_menu.add_check_item(item, i, view_menu_items[item])
i += 1
view_menu.set_item_checked(2, true) # Show Rulers
view_menu.set_item_checked(3, true) # Show Guides
view_menu.set_item_checked(4, true) # Show Animation Timeline
view_menu.hide_on_checkable_item_selection = false
i = 0
for item in image_menu_items.keys():
image_menu.add_item(item, i, image_menu_items[item])
if i == 4:
image_menu.add_separator()
i += 1
i = 0
for item in help_menu_items.keys():
help_menu.add_item(item, i, help_menu_items[item])
i += 1
file_menu.connect("id_pressed", self, "file_menu_id_pressed")
edit_menu.connect("id_pressed", self, "edit_menu_id_pressed")
view_menu.connect("id_pressed", self, "view_menu_id_pressed")
image_menu.connect("id_pressed", self, "image_menu_id_pressed")
help_menu.connect("id_pressed", self, "help_menu_id_pressed")
var root = get_tree().get_root()
# Node, left mouse shortcut, right mouse shortcut
tools.append([Global.find_node_by_name(root, "Pencil"), "left_pencil_tool", "right_pencil_tool"])
tools.append([Global.find_node_by_name(root, "Eraser"), "left_eraser_tool", "right_eraser_tool"])
tools.append([Global.find_node_by_name(root, "Bucket"), "left_fill_tool", "right_fill_tool"])
tools.append([Global.find_node_by_name(root, "LightenDarken"), "left_lightdark_tool", "right_lightdark_tool"])
tools.append([Global.find_node_by_name(root, "RectSelect"), "left_rectangle_select_tool", "right_rectangle_select_tool"])
tools.append([Global.find_node_by_name(root, "ColorPicker"), "left_colorpicker_tool", "right_colorpicker_tool"])
tools.append([Global.find_node_by_name(root, "Zoom"), "left_zoom_tool", "right_zoom_tool"])
for t in tools:
t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]])
Global.update_hint_tooltips()
# Checks to see if it's 3.1.x
if Engine.get_version_info().major == 3 and Engine.get_version_info().minor < 2:
Global.left_color_picker.get_picker().move_child(Global.left_color_picker.get_picker().get_child(0), 1)
Global.right_color_picker.get_picker().move_child(Global.right_color_picker.get_picker().get_child(0), 1)
if OS.get_cmdline_args():
for arg in OS.get_cmdline_args():
if arg.get_extension().to_lower() == "pxo":
_on_OpenSprite_file_selected(arg)
else:
$ImportSprites._on_ImportSprites_files_selected([arg])
Global.window_title = "(" + tr("untitled") + ") - Pixelorama"
Global.layers[0][0] = tr("Layer") + " 0"
Global.layers_container.get_child(0).label.text = Global.layers[0][0]
Global.layers_container.get_child(0).line_edit.text = Global.layers[0][0]
Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order())
Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order())
Global.left_color_picker.get_picker().presets_visible = false
Global.right_color_picker.get_picker().presets_visible = false
$QuitAndSaveDialog.add_button("Save & Exit", false, "Save")
$QuitAndSaveDialog.get_ok().text = "Exit without saving"
if not Global.config_cache.has_section_key("preferences", "startup"):
Global.config_cache.set_value("preferences", "startup", true)
if Global.config_cache.get_value("preferences", "startup"):
# Wait for the window to adjust itself, so the popup is correctly centered
yield(get_tree().create_timer(0.01), "timeout")
$SplashDialog.popup_centered() # Splash screen
else:
Global.can_draw = true
if not Global.config_cache.has_section_key("preferences", "open_last_project"):
Global.config_cache.set_value("preferences", "open_last_project", true)
if Global.config_cache.get_value("preferences", "open_last_project"):
Global.open_last_project = Global.config_cache.get_value("preferences", "open_last_project")
# If backup file exists then Pixelorama was not closed properly (probably crashed) - reopen backup
$BackupConfirmation.get_cancel().text = tr("Delete")
if Global.config_cache.has_section("backups"):
var project_paths = Global.config_cache.get_section_keys("backups")
if project_paths.size() > 0:
# Get backup path
var backup_path = Global.config_cache.get_value("backups", project_paths[0])
# Temporatily stop autosave until user confirms backup
OpenSave.autosave_timer.stop()
Global.can_draw = false
# For it's only possible to reload the first found backup
$BackupConfirmation.dialog_text = $BackupConfirmation.dialog_text % project_paths[0]
$BackupConfirmation.connect("confirmed", self, "_on_BackupConfirmation_confirmed", [project_paths[0], backup_path])
$BackupConfirmation.get_cancel().connect("pressed", self, "_on_BackupConfirmation_delete", [project_paths[0], backup_path])
$BackupConfirmation.popup_centered()
else:
if Global.open_last_project:
load_last_project()
else:
if Global.open_last_project:
load_last_project()
func _input(event : InputEvent) -> void:
Global.left_cursor.position = get_global_mouse_position() + Vector2(-32, 32)
Global.left_cursor.texture = Global.left_cursor_tool_texture
Global.right_cursor.position = get_global_mouse_position() + Vector2(32, 32)
Global.right_cursor.texture = Global.right_cursor_tool_texture
if event is InputEventKey and (event.scancode == KEY_ENTER or event.scancode == KEY_KP_ENTER):
if get_focus_owner() is LineEdit:
get_focus_owner().release_focus()
if event.is_action_pressed("toggle_fullscreen"):
OS.window_fullscreen = !OS.window_fullscreen
if event.is_action_pressed("redo_secondary"): # Shift + Ctrl + Z
redone = true
Global.undo_redo.redo()
redone = false
if Global.has_focus:
if event.is_action_pressed("undo") or event.is_action_pressed("redo") or event.is_action_pressed("redo_secondary"):
return
for t in tools: # Handle tool shortcuts
if event.is_action_pressed(t[2]): # Shortcut for right button (with Alt)
_on_Tool_pressed(t[0], false, false)
elif event.is_action_pressed(t[1]): # Shortcut for left button
_on_Tool_pressed(t[0], false, true)
func _notification(what : int) -> void:
if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST: # Handle exit
show_quit_dialog()
func file_menu_id_pressed(id : int) -> void:
match id:
0: # New
if Global.project_has_changed:
unsaved_canvas_state = id
$UnsavedCanvasDialog.popup_centered()
else:
$CreateNewImage.popup_centered()
Global.can_draw = false
1: # Open
$OpenSprite.popup_centered()
Global.can_draw = false
opensprite_file_selected = false
2: # Open last project
# Check if last project path is set and if yes then open
if Global.config_cache.has_section_key("preferences", "last_project_path"):
if Global.project_has_changed:
unsaved_canvas_state = id
$UnsavedCanvasDialog.popup_centered()
else:
load_last_project()
else: # if not then warn user that he didn't edit any project yet
$NoProjectEditedOrCreatedAlertDialog.popup_centered()
3: # Save
is_quitting_on_save = false
if OpenSave.current_save_path == "":
$SaveSprite.popup_centered()
Global.can_draw = false
else:
_on_SaveSprite_file_selected(OpenSave.current_save_path)
4: # Save as
is_quitting_on_save = false
$SaveSprite.popup_centered()
Global.can_draw = false
5: # Import
$ImportSprites.popup_centered()
Global.can_draw = false
opensprite_file_selected = false
6: # Export
if $ExportDialog.was_exported == false:
$ExportDialog.popup_centered()
Global.can_draw = false
else:
$ExportDialog.external_export()
7: # Export as
$ExportDialog.popup_centered()
Global.can_draw = false
8: # Quit
show_quit_dialog()
func edit_menu_id_pressed(id : int) -> void:
match id:
0: # Undo
Global.undo_redo.undo()
1: # Redo
redone = true
Global.undo_redo.redo()
redone = false
2: # Clear selection
Global.canvas.handle_undo("Rectangle Select")
Global.selection_rectangle.polygon[0] = Vector2.ZERO
Global.selection_rectangle.polygon[1] = Vector2.ZERO
Global.selection_rectangle.polygon[2] = Vector2.ZERO
Global.selection_rectangle.polygon[3] = Vector2.ZERO
Global.selected_pixels.clear()
Global.canvas.handle_redo("Rectangle Select")
3: # Preferences
$PreferencesDialog.popup_centered(Vector2(400, 280))
Global.can_draw = false
func view_menu_id_pressed(id : int) -> void:
match id:
0: # Tile mode
Global.tile_mode = !Global.tile_mode
view_menu.set_item_checked(0, Global.tile_mode)
1: # Show grid
Global.draw_grid = !Global.draw_grid
view_menu.set_item_checked(1, Global.draw_grid)
2: # Show rulers
Global.show_rulers = !Global.show_rulers
view_menu.set_item_checked(2, Global.show_rulers)
Global.horizontal_ruler.visible = Global.show_rulers
Global.vertical_ruler.visible = Global.show_rulers
3: # Show guides
Global.show_guides = !Global.show_guides
view_menu.set_item_checked(3, Global.show_guides)
for canvas in Global.canvases:
for guide in canvas.get_children():
if guide is Guide:
guide.visible = Global.show_guides
4: # Show animation timeline
Global.show_animation_timeline = !Global.show_animation_timeline
view_menu.set_item_checked(4, Global.show_animation_timeline)
Global.animation_timeline.visible = Global.show_animation_timeline
Global.canvas.update()
func image_menu_id_pressed(id : int) -> void:
if Global.layers[Global.current_layer][2]: # No changes if the layer is locked
return
match id:
0: # Scale Image
$ScaleImage.popup_centered()
Global.can_draw = false
1: # Crop Image
# Use first layer as a starting rectangle
var used_rect : Rect2 = Global.canvas.layers[0][0].get_used_rect()
# However, if first layer is empty, loop through all layers until we find one that isn't
var i := 0
while(i < Global.canvas.layers.size() - 1 and Global.canvas.layers[i][0].get_used_rect() == Rect2(0, 0, 0, 0)):
i += 1
used_rect = Global.canvas.layers[i][0].get_used_rect()
# Merge all layers with content
for j in range(Global.canvas.layers.size() - 1, i, -1):
if Global.canvas.layers[j][0].get_used_rect() != Rect2(0, 0, 0, 0):
used_rect = used_rect.merge(Global.canvas.layers[j][0].get_used_rect())
# If no layer has any content, just return
if used_rect == Rect2(0, 0, 0, 0):
return
var width := used_rect.size.x
var height := used_rect.size.y
Global.undos += 1
Global.undo_redo.create_action("Scale")
Global.undo_redo.add_do_property(Global.canvas, "size", Vector2(width, height).floor())
# Loop through all the layers to crop them
for j in range(Global.canvas.layers.size() - 1, -1, -1):
var sprite : Image = Global.canvas.layers[j][0].get_rect(used_rect)
Global.undo_redo.add_do_property(Global.canvas.layers[j][0], "data", sprite.data)
Global.undo_redo.add_undo_property(Global.canvas.layers[j][0], "data", Global.canvas.layers[j][0].data)
Global.undo_redo.add_undo_property(Global.canvas, "size", Global.canvas.size)
Global.undo_redo.add_undo_method(Global, "undo", [Global.canvas])
Global.undo_redo.add_do_method(Global, "redo", [Global.canvas])
Global.undo_redo.commit_action()
2: # Flip Horizontal
var canvas : Canvas = Global.canvas
canvas.handle_undo("Draw")
canvas.layers[Global.current_layer][0].unlock()
canvas.layers[Global.current_layer][0].flip_x()
canvas.layers[Global.current_layer][0].lock()
canvas.handle_redo("Draw")
3: # Flip Vertical
var canvas : Canvas = Global.canvas
canvas.handle_undo("Draw")
canvas.layers[Global.current_layer][0].unlock()
canvas.layers[Global.current_layer][0].flip_y()
canvas.layers[Global.current_layer][0].lock()
canvas.handle_redo("Draw")
4: # Rotate
var image : Image = Global.canvas.layers[Global.current_layer][0]
$RotateImage.set_sprite(image)
$RotateImage.popup_centered()
Global.can_draw = false
5: # Invert Colors
var image : Image = Global.canvas.layers[Global.current_layer][0]
Global.canvas.handle_undo("Draw")
for xx in image.get_size().x:
for yy in image.get_size().y:
var px_color = image.get_pixel(xx, yy).inverted()
if px_color.a == 0:
continue
image.set_pixel(xx, yy, px_color)
Global.canvas.handle_redo("Draw")
6: # Desaturation
var image : Image = Global.canvas.layers[Global.current_layer][0]
Global.canvas.handle_undo("Draw")
for xx in image.get_size().x:
for yy in image.get_size().y:
var px_color = image.get_pixel(xx, yy)
if px_color.a == 0:
continue
var gray = image.get_pixel(xx, yy).v
px_color = Color(gray, gray, gray, px_color.a)
image.set_pixel(xx, yy, px_color)
Global.canvas.handle_redo("Draw")
7: # Outline
$OutlineDialog.popup_centered()
Global.can_draw = false
8: # HSV
$HSVDialog.popup_centered()
Global.can_draw = false
func help_menu_id_pressed(id : int) -> void:
match id:
0: # Splash Screen
$SplashDialog.popup_centered()
Global.can_draw = false
1: # Issue Tracker
OS.shell_open("https://github.com/Orama-Interactive/Pixelorama/issues")
2: # Changelog
OS.shell_open("https://github.com/Orama-Interactive/Pixelorama/blob/master/Changelog.md#v062---17-02-2020")
3: # About Pixelorama
$AboutDialog.popup_centered()
Global.can_draw = false
func load_last_project() -> void:
# Check if any project was saved or opened last time
if Global.config_cache.has_section_key("preferences", "last_project_path"):
# Check if file still exists on disk
var file_path = Global.config_cache.get_value("preferences", "last_project_path")
var file_check := File.new()
if file_check.file_exists(file_path): # If yes then load the file
_on_OpenSprite_file_selected(file_path)
else:
# If file doesn't exist on disk then warn user about this
$OpenLastProjectAlertDialog.popup_centered()
func _on_UnsavedCanvasDialog_confirmed() -> void:
if unsaved_canvas_state == 0: # New image
$CreateNewImage.popup_centered()
elif unsaved_canvas_state == 2: # Open last project
load_last_project()
func _on_OpenSprite_file_selected(path : String) -> void:
OpenSave.open_pxo_file(path)
$SaveSprite.current_path = path
# Set last opened project path and save
Global.config_cache.set_value("preferences", "last_project_path", path)
Global.config_cache.save("user://cache.ini")
$ExportDialog.file_name = path.get_file().trim_suffix(".pxo")
$ExportDialog.directory_path = path.get_base_dir()
$ExportDialog.was_exported = false
file_menu.set_item_text(3, tr("Save") + " %s" % path.get_file())
file_menu.set_item_text(6, tr("Export"))
func _on_SaveSprite_file_selected(path : String) -> void:
OpenSave.save_pxo_file(path, false)
# Set last opened project path and save
Global.config_cache.set_value("preferences", "last_project_path", path)
Global.config_cache.save("user://cache.ini")
$ExportDialog.file_name = path.get_file().trim_suffix(".pxo")
$ExportDialog.directory_path = path.get_base_dir()
$ExportDialog.was_exported = false
file_menu.set_item_text(3, tr("Save") + " %s" % path.get_file())
if is_quitting_on_save:
_on_QuitDialog_confirmed()
func _on_ImportSprites_popup_hide() -> void:
if !opensprite_file_selected:
Global.can_draw = true
func _on_ViewportContainer_mouse_entered() -> void:
Global.has_focus = true
func _on_ViewportContainer_mouse_exited() -> void:
Global.has_focus = false
func _can_draw_true() -> void:
Global.can_draw = true
func _can_draw_false() -> void:
Global.can_draw = false
func _on_Tool_pressed(tool_pressed : BaseButton, mouse_press := true, key_for_left := true) -> void:
var current_action := tool_pressed.name
if (mouse_press and Input.is_action_just_released("left_mouse")) or (!mouse_press and key_for_left):
Global.current_left_tool = current_action
# Start from 1, so the label won't get invisible
for i in range(1, Global.left_tool_options_container.get_child_count()):
Global.left_tool_options_container.get_child(i).visible = false
Global.left_tool_options_container.get_node("EmptySpacer").visible = true
# Tool options visible depending on the selected tool
if current_action == "Pencil":
Global.left_brush_type_container.visible = true
Global.left_brush_size_slider.visible = true
Global.left_pixel_perfect_container.visible = true
Global.left_mirror_container.visible = true
if Global.current_left_brush_type == Global.Brush_Types.FILE or Global.current_left_brush_type == Global.Brush_Types.CUSTOM or Global.current_left_brush_type == Global.Brush_Types.RANDOM_FILE:
Global.left_color_interpolation_container.visible = true
elif current_action == "Eraser":
Global.left_brush_type_container.visible = true
Global.left_brush_size_slider.visible = true
Global.left_pixel_perfect_container.visible = true
Global.left_mirror_container.visible = true
elif current_action == "Bucket":
Global.left_fill_area_container.visible = true
Global.left_mirror_container.visible = true
elif current_action == "LightenDarken":
Global.left_brush_type_container.visible = true
Global.left_brush_size_slider.visible = true
Global.left_pixel_perfect_container.visible = true
Global.left_ld_container.visible = true
Global.left_mirror_container.visible = true
elif current_action == "ColorPicker":
Global.left_colorpicker_container.visible = true
elif current_action == "Zoom":
Global.left_zoom_container.visible = true
elif (mouse_press and Input.is_action_just_released("right_mouse")) or (!mouse_press and !key_for_left):
Global.current_right_tool = current_action
# Start from 1, so the label won't get invisible
for i in range(1, Global.right_tool_options_container.get_child_count()):
Global.right_tool_options_container.get_child(i).visible = false
Global.right_tool_options_container.get_node("EmptySpacer").visible = true
# Tool options visible depending on the selected tool
if current_action == "Pencil":
Global.right_brush_type_container.visible = true
Global.right_brush_size_slider.visible = true
Global.right_pixel_perfect_container.visible = true
Global.right_mirror_container.visible = true
if Global.current_right_brush_type == Global.Brush_Types.FILE or Global.current_right_brush_type == Global.Brush_Types.CUSTOM or Global.current_right_brush_type == Global.Brush_Types.RANDOM_FILE:
Global.right_color_interpolation_container.visible = true
elif current_action == "Eraser":
Global.right_brush_type_container.visible = true
Global.right_brush_size_slider.visible = true
Global.right_pixel_perfect_container.visible = true
Global.right_mirror_container.visible = true
elif current_action == "Bucket":
Global.right_fill_area_container.visible = true
Global.right_mirror_container.visible = true
elif current_action == "LightenDarken":
Global.right_brush_type_container.visible = true
Global.right_brush_size_slider.visible = true
Global.right_pixel_perfect_container.visible = true
Global.right_ld_container.visible = true
Global.right_mirror_container.visible = true
elif current_action == "ColorPicker":
Global.right_colorpicker_container.visible = true
elif current_action == "Zoom":
Global.right_zoom_container.visible = true
for t in tools:
var tool_name : String = t[0].name
if tool_name == Global.current_left_tool and tool_name == Global.current_right_tool:
t[0].texture_normal = load("res://Assets/Graphics/%s Themes/Tools/%s_l_r.png" % [Global.theme_type, tool_name])
elif tool_name == Global.current_left_tool:
t[0].texture_normal = load("res://Assets/Graphics/%s Themes/Tools/%s_l.png" % [Global.theme_type, tool_name])
elif tool_name == Global.current_right_tool:
t[0].texture_normal = load("res://Assets/Graphics/%s Themes/Tools/%s_r.png" % [Global.theme_type, tool_name])
else:
t[0].texture_normal = load("res://Assets/Graphics/%s Themes/Tools/%s.png" % [Global.theme_type, tool_name])
Global.left_cursor_tool_texture.create_from_image(load("res://Assets/Graphics/Tool Cursors/%s_Cursor.png" % Global.current_left_tool), 0)
Global.right_cursor_tool_texture.create_from_image(load("res://Assets/Graphics/Tool Cursors/%s_Cursor.png" % Global.current_right_tool), 0)
func _on_LeftBrushTypeButton_pressed() -> void:
Global.brushes_popup.popup(Rect2(Global.left_brush_type_button.rect_global_position, Vector2(226, 72)))
Global.brush_type_window_position = "left"
func _on_RightBrushTypeButton_pressed() -> void:
Global.brushes_popup.popup(Rect2(Global.right_brush_type_button.rect_global_position, Vector2(226, 72)))
Global.brush_type_window_position = "right"
func _on_LeftBrushSizeEdit_value_changed(value) -> void:
Global.left_brush_size_edit.value = value
Global.left_brush_size_slider.value = value
var new_size = int(value)
Global.left_brush_size = new_size
update_left_custom_brush()
func _on_RightBrushSizeEdit_value_changed(value) -> void:
Global.right_brush_size_edit.value = value
Global.right_brush_size_slider.value = value
var new_size = int(value)
Global.right_brush_size = new_size
update_right_custom_brush()
func _on_Brush_Selected() -> void:
$BrushesPopup.hide()
func _on_ColorSwitch_pressed() -> void:
var temp: Color = Global.left_color_picker.color
Global.left_color_picker.color = Global.right_color_picker.color
Global.right_color_picker.color = temp
update_left_custom_brush()
update_right_custom_brush()
func _on_ColorDefaults_pressed() -> void:
Global.left_color_picker.color = Color.black
Global.right_color_picker.color = Color.white
update_left_custom_brush()
update_right_custom_brush()
func _on_LeftColorPickerButton_color_changed(color : Color) -> void:
# If the color changed while it's on full transparency, make it opaque (GH issue #54)
if color.a == 0:
if previous_left_color.r != color.r or previous_left_color.g != color.g or previous_left_color.b != color.b:
Global.left_color_picker.color.a = 1
update_left_custom_brush()
previous_left_color = color
func _on_RightColorPickerButton_color_changed(color : Color) -> void:
# If the color changed while it's on full transparency, make it opaque (GH issue #54)
if color.a == 0:
if previous_right_color.r != color.r or previous_right_color.g != color.g or previous_right_color.b != color.b:
Global.right_color_picker.color.a = 1
update_right_custom_brush()
previous_right_color = color
func _on_LeftInterpolateFactor_value_changed(value : float) -> void:
Global.left_interpolate_spinbox.value = value
Global.left_interpolate_slider.value = value
update_left_custom_brush()
func _on_RightInterpolateFactor_value_changed(value : float) -> void:
Global.right_interpolate_spinbox.value = value
Global.right_interpolate_slider.value = value
update_right_custom_brush()
func update_left_custom_brush() -> void:
Global.update_left_custom_brush()
func update_right_custom_brush() -> void:
Global.update_right_custom_brush()
func _on_LeftFillAreaOptions_item_selected(ID : int) -> void:
Global.left_fill_area = ID
func _on_LeftFillWithOptions_item_selected(ID : int) -> void:
Global.left_fill_with = ID
if ID == 1:
Global.left_fill_pattern_container.visible = true
else:
Global.left_fill_pattern_container.visible = false
func _on_LeftPatternTypeButton_pressed() -> void:
Global.pattern_window_position = "left"
Global.patterns_popup.popup(Rect2(Global.left_brush_type_button.rect_global_position, Vector2(226, 72)))
func _on_LeftPatternOffsetX_value_changed(value : float) -> void:
Global.left_fill_pattern_offset.x = value
func _on_LeftPatternOffsetY_value_changed(value : float) -> void:
Global.left_fill_pattern_offset.y = value
func _on_RightPatternOffsetX_value_changed(value : float) -> void:
Global.right_fill_pattern_offset.x = value
func _on_RightPatternOffsetY_value_changed(value : float) -> void:
Global.right_fill_pattern_offset.y = value
func _on_RightFillAreaOptions_item_selected(ID : int) -> void:
Global.right_fill_area = ID
func _on_RightFillWithOptions_item_selected(ID : int) -> void:
Global.right_fill_with = ID
if ID == 1:
Global.right_fill_pattern_container.visible = true
else:
Global.right_fill_pattern_container.visible = false
func _on_RightPatternTypeButton_pressed() -> void:
Global.pattern_window_position = "right"
Global.patterns_popup.popup(Rect2(Global.right_brush_type_button.rect_global_position, Vector2(226, 72)))
func _on_LeftLightenDarken_item_selected(ID : int) -> void:
Global.left_ld = ID
func _on_LeftLDAmountSpinbox_value_changed(value : float) -> void:
Global.left_ld_amount = value / 100
Global.left_ld_amount_slider.value = value
Global.left_ld_amount_spinbox.value = value
func _on_RightLightenDarken_item_selected(ID : int) -> void:
Global.right_ld = ID
func _on_RightLDAmountSpinbox_value_changed(value : float) -> void:
Global.right_ld_amount = value / 100
Global.right_ld_amount_slider.value = value
Global.right_ld_amount_spinbox.value = value
func _on_LeftForColorOptions_item_selected(ID : int) -> void:
Global.left_color_picker_for = ID
func _on_RightForColorOptions_item_selected(ID : int) -> void:
Global.right_color_picker_for = ID
func _on_LeftZoomModeOptions_item_selected(ID : int) -> void:
Global.left_zoom_mode = ID
func _on_RightZoomModeOptions_item_selected(ID : int) -> void:
Global.right_zoom_mode = ID
func _on_FitToFrameButton_pressed() -> void:
var bigger_canvas_axis = max(Global.canvas.size.x, Global.canvas.size.y)
var smaller_viewport_axis = min(Global.main_viewport.rect_size.x, Global.main_viewport.rect_size.y)
Global.camera.zoom = Vector2(bigger_canvas_axis, bigger_canvas_axis) / smaller_viewport_axis
Global.camera.offset = Global.canvas.size / 2
Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %"
Global.horizontal_ruler.update()
Global.vertical_ruler.update()
func _on_100ZoomButton_pressed() -> void:
Global.camera.zoom = Vector2.ONE
Global.camera.offset = Global.canvas.size / 2
Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %"
Global.horizontal_ruler.update()
Global.vertical_ruler.update()
func _on_LeftHorizontalMirroring_toggled(button_pressed) -> void:
Global.left_horizontal_mirror = button_pressed
func _on_LeftVerticalMirroring_toggled(button_pressed) -> void:
Global.left_vertical_mirror = button_pressed
func _on_RightHorizontalMirroring_toggled(button_pressed) -> void:
Global.right_horizontal_mirror = button_pressed
func _on_RightVerticalMirroring_toggled(button_pressed) -> void:
Global.right_vertical_mirror = button_pressed
func show_quit_dialog() -> void:
if !$QuitDialog.visible:
if !Global.project_has_changed:
$QuitDialog.call_deferred("popup_centered")
else:
$QuitAndSaveDialog.call_deferred("popup_centered")
Global.can_draw = false
func _on_QuitAndSaveDialog_custom_action(action : String) -> void:
if action == "Save":
is_quitting_on_save = true
$SaveSprite.popup_centered()
$QuitDialog.hide()
Global.can_draw = false
OpenSave.remove_backup()
func _on_QuitDialog_confirmed() -> void:
# Darken the UI to denote that the application is currently exiting
# (it won't respond to user input in this state).
modulate = Color(0.5, 0.5, 0.5)
OpenSave.remove_backup()
get_tree().quit()
func _on_BackupConfirmation_confirmed(project_path : String, backup_path : String) -> void:
OpenSave.reload_backup_file(project_path, backup_path)
OpenSave.autosave_timer.start()
$ExportDialog.file_name = OpenSave.current_save_path.get_file().trim_suffix(".pxo")
$ExportDialog.directory_path = OpenSave.current_save_path.get_base_dir()
$ExportDialog.was_exported = false
file_menu.set_item_text(3, tr("Save") + " %s" % OpenSave.current_save_path.get_file())
file_menu.set_item_text(6, tr("Export"))
func _on_BackupConfirmation_delete(project_path : String, backup_path : String) -> void:
OpenSave.remove_backup_by_path(project_path, backup_path)
OpenSave.autosave_timer.start()
# Reopen last project
if Global.open_last_project:
load_last_project()
func _on_LeftPixelPerfectMode_toggled(button_pressed : bool) -> void:
Global.left_pixel_perfect = button_pressed
func _on_RightPixelPerfectMode_toggled(button_pressed : bool) -> void:
Global.right_pixel_perfect = button_pressed

1773
src/Main.tscn Normal file

File diff suppressed because one or more lines are too long

12
src/NotificationLabel.gd Normal file
View file

@ -0,0 +1,12 @@
extends Label
func _ready() -> void:
var tween := $Tween
tween.interpolate_property(self, "rect_position", rect_position, Vector2(rect_position.x, rect_position.y - 100), 1, Tween.TRANS_LINEAR, Tween.EASE_OUT)
tween.interpolate_property(self, "modulate", modulate, Color(modulate.r, modulate.g, modulate.b, 0), 1, Tween.TRANS_LINEAR, Tween.EASE_OUT)
tween.start()
func _on_Timer_timeout() -> void:
queue_free()

View file

@ -0,0 +1,21 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://src/NotificationLabel.gd" type="Script" id=2]
[node name="NotificationLabel" type="Label"]
margin_right = 116.0
margin_bottom = 14.0
custom_colors/font_color_shadow = Color( 0, 0, 0, 1 )
text = "Undo: Notification"
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Tween" type="Tween" parent="."]
[node name="Timer" type="Timer" parent="."]
one_shot = true
autostart = true
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]

View file

@ -0,0 +1,190 @@
extends WindowDialog
var palette_button = preload("res://src/Palette/PaletteButton.tscn")
var current_palette : String
var current_swatch := -1
var working_palette : Palette
onready var color_picker = $VBoxContainer/HBoxContainer/EditPaletteColorPicker
onready var palette_grid = $VBoxContainer/HBoxContainer/VBoxContainer/Panel/ScrollContainer/EditPaletteGridContainer
onready var color_name_edit = $VBoxContainer/PaletteOptions/EditPaletteColorNameLineEdit
onready var palette_name_edit = $VBoxContainer/PaletteOptions/EditPaletteNameLineEdit
onready var left_color_button = $VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer/LeftColor/NinePatchRect
onready var right_color_button = $VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer/RightColor/NinePatchRect
func _ready() -> void:
$VBoxContainer/HBoxContainer/EditPaletteColorPicker.presets_visible = false
func open(palette : String) -> void:
current_palette = palette
palette_name_edit.text = current_palette
if Global.palettes.has(palette):
working_palette = Global.palettes[palette].duplicate()
_display_palette()
Global.can_draw = false
self.popup_centered()
left_color_button.modulate = Global.left_color_picker.color
right_color_button.modulate = Global.right_color_picker.color
func _display_palette() -> void:
_clear_swatches()
var index := 0
for color_data in working_palette.colors:
var color = color_data.color
var new_button = palette_button.instance()
new_button.color = color
new_button.get_child(0).modulate = color
new_button.hint_tooltip = color_data.data.to_upper() + " " + color_data.name
new_button.draggable = true
new_button.index = index
new_button.connect("on_drop_data", self, "on_move_swatch")
new_button.connect("pressed", self, "on_swatch_select", [new_button])
palette_grid.add_child(new_button)
index += 1
if index > 0: # If there are colors, select the first
on_swatch_select(palette_grid.get_child(0))
func _clear_swatches() -> void:
for child in palette_grid.get_children():
if child is BaseButton:
child.disconnect("on_drop_data", self, "on_move_swatch")
child.queue_free()
func on_swatch_select(new_button) -> void:
current_swatch = new_button.index
color_name_edit.text = working_palette.get_color_name(current_swatch)
color_picker.color = working_palette.get_color(current_swatch)
func on_move_swatch(from : int, to : int) -> void:
working_palette.move_color(from, to)
palette_grid.move_child(palette_grid.get_child(from), to)
current_swatch = to
re_index_swatches()
func _on_AddSwatchButton_pressed() -> void:
var color : Color = color_picker.color
var new_index : int = working_palette.colors.size()
working_palette.add_color(color)
var new_button = palette_button.instance()
new_button.color = color
new_button.get_child(0).modulate = color
new_button.hint_tooltip = "#" + working_palette.get_color_data(new_index).to_upper() + " " + working_palette.get_color_name(new_index)
new_button.draggable = true
var index : int = palette_grid.get_child_count()
new_button.index = index
new_button.connect("on_drop_data", self, "on_move_swatch")
new_button.connect("pressed", self, "on_swatch_select", [new_button])
palette_grid.add_child(new_button)
on_swatch_select(new_button)
func _on_RemoveSwatchButton_pressed() -> void:
if working_palette.colors.size() > 0:
working_palette.remove_color(current_swatch)
palette_grid.remove_child(palette_grid.get_child(current_swatch))
re_index_swatches()
if current_swatch == working_palette.colors.size():
current_swatch -= 1
if current_swatch >= 0:
on_swatch_select(palette_grid.get_child(current_swatch))
func re_index_swatches() -> void:
# Re-index swatches with new order
var index := 0
for child in palette_grid.get_children():
child.index = index
index += 1
# Rename a palette, copying to user directory if necessary.
func rename_palette_file_with_priority_dirs(old_fname: String, new_fname: String) -> void:
var user_write_directory: String = Global.directory_module.get_palette_write_path()
var usrwrite_dir := Directory.new()
usrwrite_dir.open(user_write_directory)
if usrwrite_dir.file_exists(old_fname):
usrwrite_dir.rename(old_fname, new_fname)
else:
# Scan through the main system directories
var priority_dirs : Array = Global.directory_module.get_palette_search_path_in_order()
var best_clone_location = Global.palette_container.get_best_palette_file_location(
priority_dirs,
old_fname
)
if best_clone_location != null:
usrwrite_dir.copy(best_clone_location, new_fname)
func _on_EditPaletteSaveButton_pressed() -> void:
if palette_name_edit.text != current_palette:
Global.palettes.erase(current_palette)
rename_palette_file_with_priority_dirs(
current_palette + ".json",
palette_name_edit.text + ".json"
)
current_palette = palette_name_edit.text
working_palette.name = current_palette
var optionbutton_index = Global.palette_option_button.selected
Global.palette_option_button.set_item_text(optionbutton_index, current_palette)
Global.palette_option_button.set_item_metadata(optionbutton_index, current_palette)
Global.palette_option_button.text = current_palette
Global.palettes[current_palette] = working_palette
Global.palette_container.on_palette_select(current_palette)
Global.palette_container.save_palette(current_palette, working_palette.name + ".json")
self.hide()
func _on_EditPaletteCancelButton_pressed() -> void:
self.hide()
func _on_EditPaletteColorNameLineEdit_text_changed(new_text : String) -> void:
if current_swatch >= 0 && current_swatch < working_palette.colors.size():
working_palette.set_color_name(current_swatch, new_text)
_refresh_hint_tooltip(current_swatch)
func _on_EditPaletteColorPicker_color_changed(color : Color) -> void:
if current_swatch >= 0 && current_swatch < working_palette.colors.size():
palette_grid.get_child(current_swatch).get_child(0).modulate = color
working_palette.set_color(current_swatch, color)
_refresh_hint_tooltip(current_swatch)
func _refresh_hint_tooltip(_index : int) -> void:
palette_grid.get_child(current_swatch).hint_tooltip = "#" + working_palette.get_color_data(current_swatch).to_upper() + " " + working_palette.get_color_name(current_swatch)
func _on_LeftColor_pressed() -> void:
color_picker.color = Global.left_color_picker.color
_on_EditPaletteColorPicker_color_changed(color_picker.color)
func _on_RightColor_pressed() -> void:
color_picker.color = Global.right_color_picker.color
_on_EditPaletteColorPicker_color_changed(color_picker.color)
func _on_EditPalettePopup_popup_hide() -> void:
Global.can_draw = true

View file

@ -0,0 +1,240 @@
[gd_scene load_steps=5 format=2]
[ext_resource path="res://src/Palette/EditPalettePopup.gd" type="Script" id=1]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/new_frame.png" type="Texture" id=2]
[ext_resource path="res://Assets/Graphics/Dark Themes/Timeline/remove_frame.png" type="Texture" id=3]
[ext_resource path="res://Assets/Graphics/Palette/palette_button_fill.png" type="Texture" id=6]
[node name="EditPalettePopup" type="WindowDialog"]
margin_right = 600.0
margin_bottom = 550.0
rect_min_size = Vector2( 600, 570 )
window_title = "Edit Palette"
script = ExtResource( 1 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 10.0
margin_top = 10.0
margin_right = -10.0
margin_bottom = -10.0
size_flags_horizontal = 3
custom_constants/separation = 8
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_right = 580.0
margin_bottom = 462.0
size_flags_vertical = 3
[node name="EditPaletteColorPicker" type="ColorPicker" parent="VBoxContainer/HBoxContainer"]
margin_left = 4.0
margin_top = 4.0
margin_right = 4.0
margin_bottom = 4.0
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
margin_left = 312.0
margin_right = 556.0
margin_bottom = 462.0
size_flags_horizontal = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Panel" type="Panel" parent="VBoxContainer/HBoxContainer/VBoxContainer"]
margin_right = 244.0
margin_bottom = 408.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer/Panel"]
margin_right = 244.0
margin_bottom = 438.0
size_flags_horizontal = 3
size_flags_vertical = 3
__meta__ = {
"_edit_use_anchors_": false
}
[node name="EditPaletteGridContainer" type="GridContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer/Panel/ScrollContainer"]
margin_right = 244.0
margin_bottom = 438.0
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 8
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/VBoxContainer"]
margin_top = 412.0
margin_right = 244.0
margin_bottom = 426.0
text = "Use current left & right colors"
align = 1
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer"]
margin_top = 430.0
margin_right = 244.0
margin_bottom = 462.0
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer"]
margin_left = 56.0
margin_right = 188.0
margin_bottom = 32.0
[node name="LeftColor" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer"]
margin_right = 64.0
margin_bottom = 32.0
rect_min_size = Vector2( 64, 32 )
mouse_default_cursor_shape = 2
[node name="NinePatchRect" type="NinePatchRect" parent="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer/LeftColor"]
modulate = Color( 0, 0, 0, 1 )
margin_left = 2.0
margin_top = 3.0
margin_right = 62.0
margin_bottom = 29.0
texture = ExtResource( 6 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="RightColor" type="Button" parent="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer"]
margin_left = 68.0
margin_right = 132.0
margin_bottom = 32.0
rect_min_size = Vector2( 64, 32 )
mouse_default_cursor_shape = 2
[node name="NinePatchRect" type="NinePatchRect" parent="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer/RightColor"]
margin_left = 2.0
margin_top = 3.0
margin_right = 62.0
margin_bottom = 29.0
texture = ExtResource( 6 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ColorButtons" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
margin_left = 560.0
margin_right = 580.0
margin_bottom = 462.0
[node name="AddSwatchButton" type="Button" parent="VBoxContainer/HBoxContainer/ColorButtons" groups=[
"UIButtons",
]]
margin_right = 20.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/HBoxContainer/ColorButtons/AddSwatchButton"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -6.0
margin_top = -6.0
margin_right = 6.0
margin_bottom = 6.0
texture = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="RemoveSwatchButton" type="Button" parent="VBoxContainer/HBoxContainer/ColorButtons" groups=[
"UIButtons",
]]
margin_top = 24.0
margin_right = 20.0
margin_bottom = 44.0
rect_min_size = Vector2( 20, 0 )
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="VBoxContainer/HBoxContainer/ColorButtons/RemoveSwatchButton"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -6.0
margin_top = -1.0
margin_right = 6.0
margin_bottom = 1.0
texture = ExtResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PaletteOptions" type="GridContainer" parent="VBoxContainer"]
margin_top = 470.0
margin_right = 580.0
margin_bottom = 522.0
columns = 2
[node name="Label" type="Label" parent="VBoxContainer/PaletteOptions"]
margin_top = 5.0
margin_right = 91.0
margin_bottom = 19.0
text = "Color Name:"
[node name="EditPaletteColorNameLineEdit" type="LineEdit" parent="VBoxContainer/PaletteOptions"]
margin_left = 95.0
margin_right = 580.0
margin_bottom = 24.0
size_flags_horizontal = 3
[node name="Label2" type="Label" parent="VBoxContainer/PaletteOptions"]
margin_top = 33.0
margin_right = 91.0
margin_bottom = 47.0
text = "Palette Name:"
[node name="EditPaletteNameLineEdit" type="LineEdit" parent="VBoxContainer/PaletteOptions"]
margin_left = 95.0
margin_top = 28.0
margin_right = 580.0
margin_bottom = 52.0
size_flags_horizontal = 3
[node name="WindowOptionsContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 530.0
margin_right = 580.0
margin_bottom = 550.0
size_flags_horizontal = 3
[node name="SpacerControl" type="Control" parent="VBoxContainer/WindowOptionsContainer"]
margin_right = 156.0
margin_bottom = 20.0
size_flags_horizontal = 3
[node name="EditPaletteSaveButton" type="Button" parent="VBoxContainer/WindowOptionsContainer"]
margin_left = 160.0
margin_right = 201.0
margin_bottom = 20.0
text = "Save"
[node name="SpacerControl2" type="Control" parent="VBoxContainer/WindowOptionsContainer"]
margin_left = 205.0
margin_right = 361.0
margin_bottom = 20.0
size_flags_horizontal = 3
[node name="EditPaletteCancelButton" type="Button" parent="VBoxContainer/WindowOptionsContainer"]
margin_left = 365.0
margin_right = 419.0
margin_bottom = 20.0
text = "Cancel"
[node name="SpacerControl3" type="Control" parent="VBoxContainer/WindowOptionsContainer"]
margin_left = 423.0
margin_right = 580.0
margin_bottom = 20.0
size_flags_horizontal = 3
[connection signal="popup_hide" from="." to="." method="_on_EditPalettePopup_popup_hide"]
[connection signal="color_changed" from="VBoxContainer/HBoxContainer/EditPaletteColorPicker" to="." method="_on_EditPaletteColorPicker_color_changed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer/LeftColor" to="." method="_on_LeftColor_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/VBoxContainer/CenterContainer/HBoxContainer/RightColor" to="." method="_on_RightColor_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ColorButtons/AddSwatchButton" to="." method="_on_AddSwatchButton_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ColorButtons/RemoveSwatchButton" to="." method="_on_RemoveSwatchButton_pressed"]
[connection signal="text_changed" from="VBoxContainer/PaletteOptions/EditPaletteColorNameLineEdit" to="." method="_on_EditPaletteColorNameLineEdit_text_changed"]
[connection signal="pressed" from="VBoxContainer/WindowOptionsContainer/EditPaletteSaveButton" to="." method="_on_EditPaletteSaveButton_pressed"]
[connection signal="pressed" from="VBoxContainer/WindowOptionsContainer/EditPaletteCancelButton" to="." method="_on_EditPaletteCancelButton_pressed"]

View file

@ -0,0 +1,25 @@
[gd_scene format=2]
[node name="NewPaletteDialog" type="ConfirmationDialog"]
margin_right = 200.0
margin_bottom = 70.0
window_title = "Create a new custom palette from existing default?"
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
margin_left = 8.0
margin_top = 8.0
margin_right = 365.0
margin_bottom = 34.0
[node name="Label" type="Label" parent="HBoxContainer2"]
margin_top = 6.0
margin_right = 91.0
margin_bottom = 20.0
text = "Palette Name:"
[node name="NewPaletteNameLineEdit" type="LineEdit" parent="HBoxContainer2"]
margin_left = 95.0
margin_right = 357.0
margin_bottom = 26.0
size_flags_horizontal = 3
expand_to_text_length = true

161
src/Palette/Palette.gd Normal file
View file

@ -0,0 +1,161 @@
class_name Palette
extends Reference
var name : String = "Custom_Palette"
var colors : Array = []
var comments : String = ""
var editable : bool = true
func get_class() -> String:
return "Palette"
func is_class(_name : String) -> bool:
return _name == "Palette" or .is_class(_name)
func insert_color(index : int, new_color : Color, _name : String = "no name") -> void:
if index <= colors.size():
var c := PaletteColor.new(new_color, _name)
colors.insert(index, c)
func add_color(new_color : Color, _name : String = "no name") -> void:
var c := PaletteColor.new(new_color, _name)
colors.push_back(c)
func remove_color(index : int) -> void:
if index < colors.size():
colors.remove(index)
func move_color(from : int, to : int) -> void:
if from < colors.size() && to < colors.size():
var c : PaletteColor = colors[from]
remove_color(from)
insert_color(to, c.color, c.name)
func get_color(index : int) -> Color:
var result := Color.black
if index < colors.size():
result = colors[index].color
return result
func set_color(index : int, new_color : Color) -> void:
if index < colors.size():
colors[index].color = new_color
func get_color_data(index : int) -> String:
var result := ""
if index < colors.size():
result = colors[index].data
return result
func has_color(color: Color) -> bool:
for palette_color in colors:
if palette_color.color == color:
return true
return false
func set_color_data(index : int, new_color : String) -> void:
if index < colors.size():
colors[index].data = new_color
func get_color_name(index : int) -> String:
var result = ""
if index < colors.size():
result = colors[index].name
return result
func set_color_name(index : int, new_name : String) -> void:
if index < colors.size():
colors[index].name = new_name
func save_to_file(path : String) -> void:
var file = File.new()
file.open(path, File.WRITE)
file.store_string(_serialize())
file.close()
func duplicate(): # -> Palette
var copy = get_script().new() # : Palette
copy.name = name
copy.comments = comments
copy.editable = editable
for color in colors:
copy.colors.push_back(color.duplicate())
return copy
func _serialize() -> String:
var result = ""
var serialize_data : Dictionary = {
"name" : name,
"comments" : comments,
"colors" : [],
"editable" : editable
}
for color in colors:
serialize_data.colors.push_back(color.toDict())
result = JSON.print(serialize_data, " ")
return result
func deserialize(input_string : String): # -> Palette
var result = get_script().new()
var result_json = JSON.parse(input_string)
if result_json.error != OK: # If parse has errors
print("Error: ", result_json.error)
print("Error Line: ", result_json.error_line)
print("Error String: ", result_json.error_string)
result = null
else: # If parse OK
var data = result_json.result
if data.has("name"): # If data is 'valid' palette file
result = get_script().new()
result.name = data.name
if data.has("comments"):
result.comments = data.comments
if data.has("editable"):
result.editable = data.editable
for color_data in data.colors:
result.add_color(color_data.data, color_data.name)
return result
func load_from_file(path : String): # -> Palette
var result = null # : Palette
var file = File.new()
if file.file_exists(path):
file.open(path, File.READ)
var text : String = file.get_as_text()
result = deserialize(text)
file.close()
return result

View file

@ -0,0 +1,27 @@
extends Button
signal on_drop_data
export var index := 0;
export var color : Color = Color.white
export var draggable := false
var drag_preview_texture = preload("res://Assets/Graphics/Palette/swatch_drag_preview.png")
func get_drag_data(_position):
var data = null
if draggable:
data = {source_index = index}
var drag_icon = TextureRect.new()
drag_icon.texture = drag_preview_texture
drag_icon.modulate = color
set_drag_preview(drag_icon)
return data
func can_drop_data(_position, _data) -> bool:
return true
func drop_data(_position, data) -> void:
emit_signal("on_drop_data", data.source_index, index)

View file

@ -0,0 +1,45 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://Themes & Styles/StyleBoxes/palette_stylebox_pressedr.tres" type="StyleBox" id=1]
[ext_resource path="res://Themes & Styles/StyleBoxes/palette_stylebox_hover.tres" type="StyleBox" id=2]
[ext_resource path="res://src/Palette/PaletteButton.gd" type="Script" id=3]
[ext_resource path="res://Themes & Styles/StyleBoxes/palette_stylebox_focus.tres" type="StyleBox" id=4]
[ext_resource path="res://Themes & Styles/StyleBoxes/palette_stylebox_normal.tres" type="StyleBox" id=5]
[ext_resource path="res://Assets/Graphics/Palette/palette_button_fill.png" type="Texture" id=6]
[sub_resource type="ImageTexture" id=1]
[node name="PaletteButton" type="Button"]
margin_right = 26.0
margin_bottom = 26.0
rect_min_size = Vector2( 26, 26 )
hint_tooltip = "Color Name"
custom_styles/hover = ExtResource( 2 )
custom_styles/pressed = ExtResource( 1 )
custom_styles/focus = ExtResource( 4 )
custom_styles/normal = ExtResource( 5 )
action_mode = 0
button_mask = 3
icon = SubResource( 1 )
script = ExtResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="NinePatchRect" type="NinePatchRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 1.0
margin_top = 1.0
margin_right = -1.0
margin_bottom = -1.0
size_flags_horizontal = 3
size_flags_vertical = 3
texture = ExtResource( 6 )
patch_margin_left = 2
patch_margin_top = 2
patch_margin_right = 2
patch_margin_bottom = 2
__meta__ = {
"_edit_use_anchors_": false
}

View file

@ -0,0 +1,54 @@
class_name PaletteColor
extends Reference
var color : Color = Color.black setget _set_color
var data : String = "" setget _set_data
var name : String = "no name"
func get_class() -> String:
return "PaletteColor"
func is_class(_name : String) -> bool:
return _name == "PaletteColor" or .is_class(_name)
func _init(new_color : Color = Color.black, new_name : String = "no name") -> void:
self.color = new_color
self.name = new_name
func _set_color(new_value : Color) -> void:
color = new_value
data = color.to_html(true)
func _set_data(new_value : String) -> void:
data = new_value
color = Color(data)
func toDict() -> Dictionary:
var result = {
"data" : data,
"name" : name
}
return result
func fromDict(input_dict : Dictionary): # -> PaletteColor
var result = get_script().new()
result.data = input_dict.data
result.name = input_dict.name
return result
func duplicate(): # -> PaletteColor
var copy = get_script().new() # : PaletteColor
copy.data = data
copy.name = name
return copy

View file

@ -0,0 +1,272 @@
extends GridContainer
const palette_button = preload("res://src/Palette/PaletteButton.tscn")
var current_palette = "Default"
var from_palette : Palette
func _ready() -> void:
_load_palettes()
# Select default palette "Default"
on_palette_select(current_palette)
var add_palette_menu : PopupMenu = Global.add_palette_button.get_child(0)
add_palette_menu.connect("id_pressed", self, "add_palette_menu_id_pressed")
func _clear_swatches() -> void:
for child in get_children():
if child is BaseButton:
child.disconnect("pressed", self, "on_color_select")
child.queue_free()
func on_palette_select(palette_name : String) -> void:
_clear_swatches()
if Global.palettes.has(palette_name): # Palette exists in memory
current_palette = palette_name
var palette : Palette = Global.palettes[palette_name]
_display_palette(palette)
func on_new_empty_palette() -> void:
Global.new_palette_dialog.window_title = "Create a new empty palette?"
Global.new_palette_name_line_edit.text = "Custom_Palette"
from_palette = null
Global.new_palette_dialog.popup_centered()
Global.can_draw = false
func on_import_palette() -> void:
Global.palette_import_file_dialog.popup_centered()
Global.can_draw = false
func on_palette_import_file_selected(path : String) -> void:
var palette : Palette = null
if path.to_lower().ends_with("json"):
palette = Palette.new().load_from_file(path)
elif path.to_lower().ends_with("gpl"):
palette = Import.import_gpl(path)
elif path.to_lower().ends_with("png"):
palette = Import.import_png_palette(path)
if palette:
if not Global.palettes.has(palette.name):
Global.palettes[palette.name] = palette
Global.palette_option_button.add_item(palette.name)
var index: int = Global.palette_option_button.get_item_count() - 1
Global.palette_option_button.set_item_metadata(index, palette.name)
Global.palette_option_button.select(index)
on_palette_select(palette.name)
save_palette(palette.name, palette.name + ".json")
else:
Global.error_dialog.set_text(tr("Error: Palette named '%s' already exists!") % palette.name)
Global.error_dialog.popup_centered()
else:
Global.error_dialog.set_text("Invalid Palette file!")
Global.error_dialog.popup_centered()
func _on_AddPalette_pressed() -> void:
Global.add_palette_button.get_child(0).popup(Rect2(Global.add_palette_button.rect_global_position, Vector2.ONE))
func on_new_palette_confirmed() -> void:
var new_palette_name : String = Global.new_palette_name_line_edit.text
var result : String = create_new_palette(new_palette_name, from_palette)
if not result.empty():
Global.error_dialog.set_text(result)
Global.error_dialog.popup_centered()
func add_palette_menu_id_pressed(id : int) -> void:
match id:
0: # New Empty Palette
Global.palette_container.on_new_empty_palette()
1: # Import Palette
Global.palette_container.on_import_palette()
func create_new_palette(name : String, _from_palette : Palette) -> String: # Returns empty string, else error string
var new_palette : Palette = Palette.new()
# Check if new name is valid
if name.empty():
return tr("Error: Palette must have a valid name.")
if Global.palettes.has(name):
return tr("Error: Palette named '%s' already exists!") % name
new_palette.name = name
# Check if source palette has data
if _from_palette:
new_palette = _from_palette.duplicate()
new_palette.name = name
new_palette.editable = true
# Add palette to Global and options
Global.palettes[name] = new_palette
Global.palette_option_button.add_item(name)
var index : int = Global.palette_option_button.get_item_count() - 1
Global.palette_option_button.set_item_metadata(index, name)
Global.palette_option_button.select(index)
save_palette(name, name + ".json")
on_palette_select(name)
return ""
func on_edit_palette() -> void:
var palette : Palette = Global.palettes[current_palette]
var create_new_palette := true # Create new palette by default
if palette.editable:
create_new_palette = false # Edit if already a custom palette
if create_new_palette:
from_palette = Global.palettes[current_palette]
Global.new_palette_dialog.window_title = "Create a new custom palette from existing default?"
Global.new_palette_name_line_edit.text = "Custom_" + current_palette
Global.new_palette_dialog.popup_centered()
Global.can_draw = false
else:
from_palette = null
Global.edit_palette_popup.open(current_palette)
func _on_PaletteOptionButton_item_selected(ID : int) -> void:
var palette_name = Global.palette_option_button.get_item_metadata(ID)
on_palette_select(palette_name)
func _display_palette(palette : Palette) -> void:
var index := 0
for color_data in palette.colors:
var color = color_data.color
var new_button = palette_button.instance()
new_button.get_child(0).modulate = color
new_button.hint_tooltip = "#" + color_data.data.to_upper() + " " + color_data.name
new_button.connect("pressed", self, "on_color_select", [index])
add_child(new_button)
index += 1
func on_color_select(index : int) -> void:
var color : Color = Global.palettes[current_palette].get_color(index)
if Input.is_action_just_pressed("left_mouse"):
Global.left_color_picker.color = color
Global.update_left_custom_brush()
elif Input.is_action_just_pressed("right_mouse"):
Global.right_color_picker.color = color
Global.update_right_custom_brush()
func _load_palettes() -> void:
Global.directory_module.ensure_xdg_user_dirs_exist()
var search_locations = Global.directory_module.get_palette_search_path_in_order()
var priority_ordered_files := get_palette_priority_file_map(search_locations)
# Iterate backwards, so any palettes defined in default files
# get overwritten by those of the same name in user files
search_locations.invert()
priority_ordered_files.invert()
for i in range(len(search_locations)):
var base_directory : String = search_locations[i]
var palette_files : Array = priority_ordered_files[i]
for file_name in palette_files:
var palette : Palette = Palette.new().load_from_file(base_directory.plus_file(file_name))
if palette:
Global.palettes[palette.name] = palette
Global.palette_option_button.add_item(palette.name)
var index: int = Global.palette_option_button.get_item_count() - 1
Global.palette_option_button.set_item_metadata(index, palette.name)
if palette.name == "Default":
Global.palette_option_button.select(index)
if not "Default" in Global.palettes && Global.palettes.size() > 0:
Global.palette_container._on_PaletteOptionButton_item_selected(0)
# Get the palette files in a single directory.
# if it does not exist, return []
func get_palette_files(path : String ) -> Array:
var dir := Directory.new()
var results = []
if not dir.dir_exists(path):
return []
dir.open(path)
dir.list_dir_begin()
while true:
var file_name = dir.get_next()
if file_name == "":
break
elif (not file_name.begins_with(".")) && file_name.to_lower().ends_with("json") && not dir.current_is_dir():
results.append(file_name)
dir.list_dir_end()
return results
# This returns an array of arrays, with priorities.
# In particular, it takes an array of paths to look for
# arrays in, in order of file and palette override priority
# such that the files in the first directory override the
# second, third, etc. ^.^
# It returns an array of arrays, where each output array
# corresponds to the given input array at the same index, and
# contains the (relative to the given directory) palette files
# to load, excluding all ones already existing in higher-priority
# directories. nya
# in particular, this also means you can run backwards on the result
# so that palettes with the given palette name in the higher priority
# directories override those set in lower priority directories :)
func get_palette_priority_file_map(looking_paths: Array) -> Array:
var final_list := []
# Holds pattern files already found
var working_file_set : Dictionary = {}
for search_directory in looking_paths:
var to_add_files := []
var files = get_palette_files(search_directory)
# files to check
for maybe_to_add in files:
if not maybe_to_add in working_file_set:
to_add_files.append(maybe_to_add)
working_file_set[maybe_to_add] = true
final_list.append(to_add_files)
return final_list
# Locate the highest priority palette by the given relative filename
# If none is found in the directories, then do nothing and return
# null
func get_best_palette_file_location(looking_paths: Array, fname: String): # -> String:
var priority_fmap : Array = get_palette_priority_file_map(looking_paths)
for i in range(len(looking_paths)):
var base_path : String = looking_paths[i]
var the_files : Array = priority_fmap[i]
if the_files.has(fname):
return base_path.plus_file(fname)
return null
func save_palette(palette_name : String, filename : String) -> void:
Global.directory_module.ensure_xdg_user_dirs_exist()
var palette = Global.palettes[palette_name]
var palettes_write_path: String = Global.directory_module.get_palette_write_path()
palette.save_to_file(palettes_write_path.plus_file(filename))
func _on_NewPaletteDialog_popup_hide() -> void:
Global.can_draw = true

View file

@ -0,0 +1,13 @@
[gd_scene format=2]
[node name="PaletteImportFileDialog" type="FileDialog"]
anchor_right = 1.0
anchor_bottom = 1.0
rect_min_size = Vector2( 500, 300 )
window_title = "Open a File"
resizable = true
mode = 0
access = 2
filters = PoolStringArray( "*.json ; JavaScript Object Notation" )
current_dir = "D:/GitHub/Pixelorama"
current_path = "D:/GitHub/Pixelorama/"

29
src/PatternButton.gd Normal file
View file

@ -0,0 +1,29 @@
extends TextureButton
var image : Image
var image_size : Vector2
var texture : ImageTexture
func _ready():
if image:
image_size = image.get_size()
texture = ImageTexture.new()
texture.create_from_image(image, 0)
func _on_PatternButton_pressed() -> void:
if Global.pattern_window_position == "left":
Global.pattern_left_image = image
Global.left_fill_pattern_container.get_child(0).get_child(0).texture = texture
Global.left_fill_pattern_container.get_child(2).get_child(1).max_value = image_size.x - 1
Global.left_fill_pattern_container.get_child(3).get_child(1).max_value = image_size.y - 1
elif Global.pattern_window_position == "right":
Global.pattern_right_image = image
Global.right_fill_pattern_container.get_child(0).get_child(0).texture = texture
Global.right_fill_pattern_container.get_child(2).get_child(1).max_value = image_size.x - 1
Global.right_fill_pattern_container.get_child(3).get_child(1).max_value = image_size.y - 1
Global.patterns_popup.hide()

28
src/PatternButton.tscn Normal file
View file

@ -0,0 +1,28 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://Assets/Graphics/Brush_button.png" type="Texture" id=1]
[ext_resource path="res://src/PatternButton.gd" type="Script" id=2]
[node name="PatternButton" type="TextureButton"]
margin_right = 32.0
margin_bottom = 32.0
rect_min_size = Vector2( 3, 0 )
button_mask = 7
texture_normal = ExtResource( 1 )
stretch_mode = 5
script = ExtResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="PatternTexture" type="TextureRect" parent="."]
margin_right = 32.0
margin_bottom = 32.0
rect_min_size = Vector2( 32, 32 )
expand = true
stretch_mode = 6
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="pressed" from="." to="." method="_on_PatternButton_pressed"]

76
src/Rulers/Guides.gd Normal file
View file

@ -0,0 +1,76 @@
class_name Guide
extends Line2D
enum Types {HORIZONTAL, VERTICAL}
var font := preload("res://Assets/Fonts/Roboto-Regular.tres")
var has_focus := true
var mouse_pos := Vector2.ZERO
var previous_points := points
var type = Types.HORIZONTAL
func _ready() -> void:
width = 0.1
default_color = Global.guide_color
func _input(_event : InputEvent):
width = Global.camera.zoom.x * 2
mouse_pos = get_local_mouse_position()
var point0 := points[0]
var point1 := points[1]
if type == Types.HORIZONTAL:
point0.y -= width * 3
point1.y += width * 3
else:
point0.x -= width * 3
point1.x += width * 3
if Global.can_draw and Global.has_focus and point_in_rectangle(mouse_pos, point0, point1) and Input.is_action_just_pressed("left_mouse"):
if !point_in_rectangle(Global.canvas.current_pixel, Global.canvas.location, Global.canvas.location + Global.canvas.size):
has_focus = true
Global.has_focus = false
update()
if has_focus:
if Input.is_action_just_pressed("left_mouse"):
previous_points = points
if Input.is_action_pressed("left_mouse"):
if type == Types.HORIZONTAL:
points[0].y = round(mouse_pos.y)
points[1].y = round(mouse_pos.y)
else:
points[0].x = round(mouse_pos.x)
points[1].x = round(mouse_pos.x)
if Input.is_action_just_released("left_mouse"):
Global.has_focus = true
has_focus = false
if !outside_canvas():
update()
func _draw() -> void:
if has_focus:
var viewport_size: Vector2 = Global.main_viewport.rect_size
var zoom: Vector2 = Global.camera.zoom
if type == Types.HORIZONTAL:
draw_set_transform(Vector2(Global.camera.offset.x - (viewport_size.x / 2) * zoom.x, points[0].y + font.get_height() * zoom.x * 2), rotation, zoom * 2)
draw_string(font, Vector2.ZERO, "%spx" % str(round(mouse_pos.y)))
else:
draw_set_transform(Vector2(points[0].x + font.get_height() * zoom.y, Global.camera.offset.y - (viewport_size.y / 2.25) * zoom.y), rotation, zoom * 2)
draw_string(font, Vector2.ZERO, "%spx" % str(round(mouse_pos.x)))
func outside_canvas() -> bool:
if type == Types.HORIZONTAL:
if points[0].y < 0 || points[0].y > Global.canvas.size.y:
queue_free()
return true
else:
if points[0].x < 0 || points[0].x > Global.canvas.size.x:
queue_free()
return true
return false
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

@ -0,0 +1,79 @@
extends Button
const RULER_WIDTH := 16
var font := preload("res://Assets/Fonts/Roboto-Small.tres")
var major_subdivision := 2
var minor_subdivision := 4
var first : Vector2
var last : Vector2
func _ready() -> void:
Global.main_viewport.connect("item_rect_changed", self, "update")
# Code taken and modified from Godot's source code
func _draw() -> void:
var transform := Transform2D()
var ruler_transform := Transform2D()
var major_subdivide := Transform2D()
var minor_subdivide := Transform2D()
var zoom: float = 1 / Global.camera.zoom.x
transform.x = Vector2(zoom, zoom)
transform.origin = Global.main_viewport.rect_size / 2 + Global.camera.offset * -zoom
var basic_rule := 100.0
var i := 0
while(basic_rule * zoom > 100):
basic_rule /= 5.0 if i % 2 else 2.0
i += 1
i = 0
while(basic_rule * zoom < 100):
basic_rule *= 2.0 if i % 2 else 5.0
i += 1
ruler_transform = ruler_transform.scaled(Vector2(basic_rule, basic_rule))
major_subdivide = major_subdivide.scaled(Vector2(1.0 / major_subdivision, 1.0 / major_subdivision))
minor_subdivide = minor_subdivide.scaled(Vector2(1.0 / minor_subdivision, 1.0 / minor_subdivision))
first = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Vector2.ZERO)
last = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Global.main_viewport.rect_size)
for i in range(ceil(first.x), ceil(last.x)):
var position : Vector2 = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Vector2(i, 0))
if i % (major_subdivision * minor_subdivision) == 0:
draw_line(Vector2(position.x + RULER_WIDTH, 0), Vector2(position.x + RULER_WIDTH, RULER_WIDTH), Color.white)
var val = (ruler_transform * major_subdivide * minor_subdivide).xform(Vector2(i, 0)).x
draw_string(font, Vector2(position.x + RULER_WIDTH + 2, font.get_height() - 4), str(int(val)))
else:
if i % minor_subdivision == 0:
draw_line(Vector2(position.x + RULER_WIDTH, RULER_WIDTH * 0.33), Vector2(position.x + RULER_WIDTH, RULER_WIDTH), Color.white)
else:
draw_line(Vector2(position.x + RULER_WIDTH, RULER_WIDTH * 0.66), Vector2(position.x + RULER_WIDTH, RULER_WIDTH), Color.white)
func _on_HorizontalRuler_pressed() -> void:
if !Global.show_guides:
return
var mouse_pos := get_local_mouse_position()
if mouse_pos.x < RULER_WIDTH: # For double guides
Global.vertical_ruler._on_VerticalRuler_pressed()
var guide := Guide.new()
guide.type = guide.Types.HORIZONTAL
guide.add_point(Vector2(-99999, Global.canvas.current_pixel.y))
guide.add_point(Vector2(99999, Global.canvas.current_pixel.y))
Global.canvas.add_child(guide)
Global.has_focus = false
update()
func _on_HorizontalRuler_mouse_entered() -> void:
var mouse_pos := get_local_mouse_position()
if mouse_pos.x < RULER_WIDTH: # For double guides
mouse_default_cursor_shape = Control.CURSOR_FDIAGSIZE
else:
mouse_default_cursor_shape = Control.CURSOR_VSPLIT

View file

@ -0,0 +1,71 @@
extends Button
const RULER_WIDTH := 16
var font := preload("res://Assets/Fonts/Roboto-Small.tres")
var major_subdivision := 2
var minor_subdivision := 4
var first : Vector2
var last : Vector2
func _ready() -> void:
Global.main_viewport.connect("item_rect_changed", self, "update")
# Code taken and modified from Godot's source code
func _draw() -> void:
var transform := Transform2D()
var ruler_transform := Transform2D()
var major_subdivide := Transform2D()
var minor_subdivide := Transform2D()
var zoom: float = 1 / Global.camera.zoom.x
transform.y = Vector2(zoom, zoom)
transform.origin = Global.main_viewport.rect_size / 2 + Global.camera.offset * -zoom
var basic_rule := 100.0
var i := 0
while(basic_rule * zoom > 100):
basic_rule /= 5.0 if i % 2 else 2.0
i += 1
i = 0
while(basic_rule * zoom < 100):
basic_rule *= 2.0 if i % 2 else 5.0
i += 1
ruler_transform = ruler_transform.scaled(Vector2(basic_rule, basic_rule))
major_subdivide = major_subdivide.scaled(Vector2(1.0 / major_subdivision, 1.0 / major_subdivision))
minor_subdivide = minor_subdivide.scaled(Vector2(1.0 / minor_subdivision, 1.0 / minor_subdivision))
first = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Vector2.ZERO)
last = (transform * ruler_transform * major_subdivide * minor_subdivide).affine_inverse().xform(Global.main_viewport.rect_size)
for i in range(ceil(first.y), ceil(last.y)):
var position : Vector2 = (transform * ruler_transform * major_subdivide * minor_subdivide).xform(Vector2(0, i))
if i % (major_subdivision * minor_subdivision) == 0:
draw_line(Vector2(0, position.y), Vector2(RULER_WIDTH, position.y), Color.white)
var text_xform = Transform2D(-PI / 2, Vector2(font.get_height() - 4, position.y - 2))
draw_set_transform_matrix(get_transform() * text_xform)
var val = (ruler_transform * major_subdivide * minor_subdivide).xform(Vector2(0, i)).y
draw_string(font, Vector2(), str(int(val)))
draw_set_transform_matrix(get_transform())
else:
if i % minor_subdivision == 0:
draw_line(Vector2(RULER_WIDTH * 0.33, position.y), Vector2(RULER_WIDTH, position.y), Color.white)
else:
draw_line(Vector2(RULER_WIDTH * 0.66, position.y), Vector2(RULER_WIDTH, position.y), Color.white)
func _on_VerticalRuler_pressed() -> void:
if !Global.show_guides:
return
var guide := Guide.new()
guide.type = guide.Types.VERTICAL
guide.add_point(Vector2(Global.canvas.current_pixel.x, -99999))
guide.add_point(Vector2(Global.canvas.current_pixel.x, 99999))
Global.canvas.add_child(guide)
Global.has_focus = false
update()

6
src/SecondViewport.gd Normal file
View file

@ -0,0 +1,6 @@
extends Viewport
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
world_2d = Global.canvas.get_parent().world_2d

149
src/SelectionRectangle.gd Normal file
View file

@ -0,0 +1,149 @@
extends Polygon2D
var img : Image
var tex : ImageTexture
var is_dragging := false
var move_pixels := false
var diff_x := 0.0
var diff_y := 0.0
var orig_x := 0.0
var orig_y := 0.0
var orig_colors := []
func _ready() -> void:
img = Image.new()
img.create(1, 1, false, Image.FORMAT_RGBA8)
img.lock()
tex = ImageTexture.new()
tex.create_from_image(img, 0)
func _process(_delta : float) -> void:
if Global.layers[Global.current_layer][2]:
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_layer
var layer : Image = Global.canvas.layers[current_layer_index][0]
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.selected_pixels.size() > 0 and (Global.current_left_tool == "RectSelect" or Global.current_right_tool == "RectSelect"):
get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_MOVE
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
if (Global.current_left_tool == "RectSelect" && Input.is_action_just_pressed("left_mouse")) || (Global.current_right_tool == "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.selected_pixels.size()):
var curr_px = Global.selected_pixels[i]
if point_in_rectangle(curr_px, Global.canvas.location - Vector2.ONE, Global.canvas.size):
orig_colors.append(layer.get_pixelv(curr_px)) # Color of pixel
var px = curr_px - Global.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()
# Makes line2d invisible
Global.canvas.line_2d.default_color = Color(0, 0, 0, 0)
else:
get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_ARROW
if is_dragging:
if (Global.current_left_tool == "RectSelect" && Input.is_action_pressed("left_mouse")) || (Global.current_right_tool == "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_left_tool == "RectSelect" && Input.is_action_just_released("left_mouse")) || (Global.current_right_tool == "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.selected_pixels[i] - Global.selected_pixels[0]
if point_in_rectangle(px, Global.canvas.location - Vector2.ONE, Global.canvas.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.selected_pixels.clear()
for xx in range(start_pos.x, end_pos.x):
for yy in range(start_pos.y, end_pos.y):
Global.selected_pixels.append(Vector2(xx, yy))
Global.canvas.handle_redo("Rectangle Select") # Redo
# Makes line2d visible
Global.canvas.line_2d.default_color = Color.darkgray
if Global.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.custom_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.canvas.size):
layer.set_pixel(xx, yy, Color(0, 0, 0, 0))
Global.canvas.handle_redo("Draw")
func _draw() -> void:
if img.get_size() == polygon[2] - polygon[0]:
draw_texture(tex, polygon[0], Color(1, 1, 1, 0.5))
func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool:
return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y

View file

@ -0,0 +1,8 @@
extends ColorRect
func _ready() -> void:
rect_size = Global.canvas.size
material.set_shader_param("size", Global.checker_size)
material.set_shader_param("color1", Global.checker_color_1)
material.set_shader_param("color2", Global.checker_color_2)

143
src/XDGDataPaths.gd Normal file
View file

@ -0,0 +1,143 @@
extends Node
# These are *with* the config subdirectory name
var xdg_data_home : String
var xdg_data_dirs : Array
# These are *without* the config subdirectory name
var raw_xdg_data_home : String
var raw_xdg_data_dirs : Array
# Default location for xdg_data_home relative to $HOME
const default_xdg_data_home_rel := ".local/share"
const default_xdg_data_dirs := ["/usr/local/share", "/usr/share"]
const config_subdir_name := "pixelorama"
const palettes_data_subdirectory := "Palettes"
const brushes_data_subdirectory := "Brushes"
const patterns_data_subdirectory := "Patterns"
# Get if we should use XDG standard or not nyaaaa
func use_xdg_standard() -> bool:
# see: https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html
# return OS.has_feature("Linux") or OS.has_feature("BSD")
# Previous was unreliable and buggy >.< nyaa
return OS.get_name() == "X11"
func _init():
if use_xdg_standard():
print("Detected system where we should use XDG basedir standard (currently Linux or BSD)")
var home := OS.get_environment("HOME")
raw_xdg_data_home = home.plus_file(
default_xdg_data_home_rel
)
xdg_data_home = raw_xdg_data_home.plus_file(
config_subdir_name
)
# Create defaults
xdg_data_dirs = []
raw_xdg_data_dirs = default_xdg_data_dirs
for default_loc in raw_xdg_data_dirs:
xdg_data_dirs.append(
default_loc.plus_file(config_subdir_name)
)
# Now check the XDG environment variables and if
# present, replace the defaults with them!
# See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
# Checks the xdg data home var
if OS.has_environment("XDG_DATA_HOME"):
raw_xdg_data_home = OS.get_environment("XDG_DATA_HOME")
xdg_data_home = raw_xdg_data_home.plus_file(config_subdir_name)
# Checks the list of files var, and processes them.
if OS.has_environment("XDG_DATA_DIRS"):
var raw_env_var := OS.get_environment("XDG_DATA_DIRS")
# includes empties.
var unappended_subdirs := raw_env_var.split(":", true)
raw_xdg_data_dirs = unappended_subdirs
xdg_data_dirs = []
for unapp_subdir in raw_xdg_data_dirs:
xdg_data_dirs.append(unapp_subdir.plus_file(config_subdir_name))
else:
raw_xdg_data_home = Global.root_directory
xdg_data_home = raw_xdg_data_home.plus_file(config_subdir_name)
raw_xdg_data_dirs = []
xdg_data_dirs = []
func append_file_to_all(basepaths: Array, subpath: String) -> Array:
var res := []
for _path in basepaths:
res.append(_path.plus_file(subpath))
return res
# Get search paths in order of priority
func get_search_paths_in_order() -> Array:
return [xdg_data_home] + xdg_data_dirs
# Gets the paths, in order of search priority, for palettes.
func get_palette_search_path_in_order() -> Array:
var base_paths := get_search_paths_in_order()
return append_file_to_all(base_paths, palettes_data_subdirectory)
# Gets the paths, in order of search priority, for brushes.
func get_brushes_search_path_in_order() -> Array:
var base_paths := get_search_paths_in_order()
return append_file_to_all(base_paths, brushes_data_subdirectory)
# Gets the paths, in order of search priority, for patterns.
func get_patterns_search_path_in_order() -> Array:
var base_paths := get_search_paths_in_order()
return append_file_to_all(base_paths, patterns_data_subdirectory)
# Get the path that we are ok to be writing palettes to:
func get_palette_write_path() -> String:
return xdg_data_home.plus_file(palettes_data_subdirectory)
# Get the path that we are ok to be writing brushes to:
func get_brushes_write_path() -> String:
return xdg_data_home.plus_file(brushes_data_subdirectory)
# Get the path that we are ok to be writing patterns to:
func get_patterns_write_path() -> String:
return xdg_data_home.plus_file(patterns_data_subdirectory)
# Ensure the user xdg directories exist:
func ensure_xdg_user_dirs_exist() -> void:
if !OS.has_feature("standalone"): # Don't execute if we're in the editor
return
var base_dir := Directory.new()
base_dir.open(raw_xdg_data_home)
# Ensure the main config directory exists.
if not base_dir.dir_exists(xdg_data_home):
base_dir.make_dir(xdg_data_home)
var actual_data_dir := Directory.new()
actual_data_dir.open(xdg_data_home)
var palette_writing_dir := get_palette_write_path()
var brushes_writing_dir := get_brushes_write_path()
var pattern_writing_dir := get_patterns_write_path()
# Create the palette and brush dirs
if not actual_data_dir.dir_exists(palette_writing_dir):
print("Making directory %s" % [palette_writing_dir])
actual_data_dir.make_dir(palette_writing_dir)
if not actual_data_dir.dir_exists(brushes_writing_dir):
print("Making directory %s" % [brushes_writing_dir])
actual_data_dir.make_dir(brushes_writing_dir)
if not actual_data_dir.dir_exists(pattern_writing_dir):
print("Making directory %s" % [pattern_writing_dir])
actual_data_dir.make_dir(pattern_writing_dir)