mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 09:09:47 +00:00
v0.4 - Undo/Redo, mirrored drawing, lighten/darken tool and more!
In this commit: - Added notification labels that appear when the user does an action (for undoing, redoing and saving) - Fixed symmetry with custom brushes. - In Main.gd, current_save_path and current_export_path get cleared when the user creates a new drawing or opens one. v0.4 of Pixelorama is out! Check https://functionoverload590613498.wordpress.com/2019/11/13/pixelorama-v0-4-is-out/ for the full changelog.
This commit is contained in:
parent
17d117a0b1
commit
d66c501b5c
|
@ -23,6 +23,7 @@ size_flags_horizontal = 3
|
||||||
custom_constants/separation = 0
|
custom_constants/separation = 0
|
||||||
|
|
||||||
[node name="ToolPanel" type="Panel" parent="UI"]
|
[node name="ToolPanel" type="Panel" parent="UI"]
|
||||||
|
editor/display_folded = true
|
||||||
margin_right = 230.0
|
margin_right = 230.0
|
||||||
margin_bottom = 600.0
|
margin_bottom = 600.0
|
||||||
rect_min_size = Vector2( 230, 0 )
|
rect_min_size = Vector2( 230, 0 )
|
||||||
|
@ -36,6 +37,7 @@ size_flags_horizontal = 3
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
|
||||||
[node name="MenusAndTools" type="VBoxContainer" parent="UI/ToolPanel/Tools"]
|
[node name="MenusAndTools" type="VBoxContainer" parent="UI/ToolPanel/Tools"]
|
||||||
|
editor/display_folded = true
|
||||||
margin_right = 230.0
|
margin_right = 230.0
|
||||||
margin_bottom = 224.0
|
margin_bottom = 224.0
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
@ -167,6 +169,7 @@ margin_right = 230.0
|
||||||
margin_bottom = 232.0
|
margin_bottom = 232.0
|
||||||
|
|
||||||
[node name="ToolOptions" type="HBoxContainer" parent="UI/ToolPanel/Tools"]
|
[node name="ToolOptions" type="HBoxContainer" parent="UI/ToolPanel/Tools"]
|
||||||
|
editor/display_folded = true
|
||||||
margin_top = 236.0
|
margin_top = 236.0
|
||||||
margin_right = 230.0
|
margin_right = 230.0
|
||||||
margin_bottom = 460.0
|
margin_bottom = 460.0
|
||||||
|
@ -174,7 +177,6 @@ size_flags_vertical = 3
|
||||||
custom_constants/separation = 0
|
custom_constants/separation = 0
|
||||||
|
|
||||||
[node name="LeftToolOptions" type="VBoxContainer" parent="UI/ToolPanel/Tools/ToolOptions"]
|
[node name="LeftToolOptions" type="VBoxContainer" parent="UI/ToolPanel/Tools/ToolOptions"]
|
||||||
editor/display_folded = true
|
|
||||||
margin_right = 113.0
|
margin_right = 113.0
|
||||||
margin_bottom = 224.0
|
margin_bottom = 224.0
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
@ -260,12 +262,14 @@ text = "C"
|
||||||
margin_top = 166.0
|
margin_top = 166.0
|
||||||
margin_right = 113.0
|
margin_right = 113.0
|
||||||
margin_bottom = 190.0
|
margin_bottom = 190.0
|
||||||
|
hint_tooltip = "Enable horizontal mirrored drawing for the left tool"
|
||||||
text = "Horiz. Mirror"
|
text = "Horiz. Mirror"
|
||||||
|
|
||||||
[node name="LeftVerticalMirroring" type="CheckBox" parent="UI/ToolPanel/Tools/ToolOptions/LeftToolOptions"]
|
[node name="LeftVerticalMirroring" type="CheckBox" parent="UI/ToolPanel/Tools/ToolOptions/LeftToolOptions"]
|
||||||
margin_top = 194.0
|
margin_top = 194.0
|
||||||
margin_right = 113.0
|
margin_right = 113.0
|
||||||
margin_bottom = 218.0
|
margin_bottom = 218.0
|
||||||
|
hint_tooltip = "Enable vertical mirrored drawing for the left tool"
|
||||||
text = "Vert. Mirror"
|
text = "Vert. Mirror"
|
||||||
|
|
||||||
[node name="VSeparator" type="VSeparator" parent="UI/ToolPanel/Tools/ToolOptions"]
|
[node name="VSeparator" type="VSeparator" parent="UI/ToolPanel/Tools/ToolOptions"]
|
||||||
|
@ -274,7 +278,6 @@ margin_right = 117.0
|
||||||
margin_bottom = 224.0
|
margin_bottom = 224.0
|
||||||
|
|
||||||
[node name="RightToolOptions" type="VBoxContainer" parent="UI/ToolPanel/Tools/ToolOptions"]
|
[node name="RightToolOptions" type="VBoxContainer" parent="UI/ToolPanel/Tools/ToolOptions"]
|
||||||
editor/display_folded = true
|
|
||||||
margin_left = 117.0
|
margin_left = 117.0
|
||||||
margin_right = 230.0
|
margin_right = 230.0
|
||||||
margin_bottom = 224.0
|
margin_bottom = 224.0
|
||||||
|
@ -360,12 +363,14 @@ text = "C"
|
||||||
margin_top = 166.0
|
margin_top = 166.0
|
||||||
margin_right = 113.0
|
margin_right = 113.0
|
||||||
margin_bottom = 190.0
|
margin_bottom = 190.0
|
||||||
|
hint_tooltip = "Enable horizontal mirrored drawing for the right tool"
|
||||||
text = "Horiz. Mirror"
|
text = "Horiz. Mirror"
|
||||||
|
|
||||||
[node name="RightVerticalMirroring" type="CheckBox" parent="UI/ToolPanel/Tools/ToolOptions/RightToolOptions"]
|
[node name="RightVerticalMirroring" type="CheckBox" parent="UI/ToolPanel/Tools/ToolOptions/RightToolOptions"]
|
||||||
margin_top = 194.0
|
margin_top = 194.0
|
||||||
margin_right = 113.0
|
margin_right = 113.0
|
||||||
margin_bottom = 218.0
|
margin_bottom = 218.0
|
||||||
|
hint_tooltip = "Enable vertical mirrored drawing for the right tool"
|
||||||
text = "Vert. Mirror"
|
text = "Vert. Mirror"
|
||||||
|
|
||||||
[node name="HSeparator2" type="HSeparator" parent="UI/ToolPanel/Tools"]
|
[node name="HSeparator2" type="HSeparator" parent="UI/ToolPanel/Tools"]
|
||||||
|
|
17
Prefabs/NotificationLabel.tscn
Normal file
17
Prefabs/NotificationLabel.tscn
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://Scripts/NotificationLabel.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[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( 1 )
|
||||||
|
|
||||||
|
[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"]
|
20
README.md
20
README.md
|
@ -1,28 +1,30 @@
|
||||||
# Pixelorama - your free and open-source sprite editor!
|
# Pixelorama - your free and open-source sprite editor!
|
||||||
A free & open-source 2D sprite editor, made with Godot Engine, using GDScript!
|
Your free & open-source 2D sprite editor, made in the Godot Engine, using GDScript!
|
||||||
|
|
||||||
[![Pixelorama's horrible UI](https://functionoverload590613498.files.wordpress.com/2019/10/screenshot_265.png)](https://www.youtube.com/watch?v=L8o8QKB8lPs)
|
[![Pixelorama's horrible UI](https://functionoverload590613498.files.wordpress.com/2019/11/screenshot_268.png)](https://www.youtube.com/watch?v=h3OJROgAR-A)
|
||||||
|
|
||||||
Current features as of version v0.3:
|
Current features as of version v0.4:
|
||||||
|
|
||||||
- Choosing between 4 tools – pencil, eraser, fill bucket and rectangle select – and mapping them to both of your left and right mouse buttons. That’s, pretty wild, huh?
|
- Choosing between 6 tools – pencil, eraser, fill bucket, paint same color, lighten/darken and rectangle select – and mapping them to both of your left and right mouse buttons.
|
||||||
- Different colors and brush sizes for each of the mouse buttons.
|
- Different colors and brush sizes for each of the mouse buttons.
|
||||||
- Custom brush support.
|
- Support of two types of custom brushes: "From files" and "per project" brushes. Custom brushes from files get loaded from the "Brushes" folder that comes with Pixelorama, and per project brushes get saved with the rectangle select tool.
|
||||||
- Creating a new canvas with a size of your choosing.
|
- Creating a new canvas with a size of your choosing.
|
||||||
- Are you an animator? Then you've come to the right place! Pixelorama has its own Animation Timeline just for you!
|
- Are you an animator? Then you've come to the right place! Pixelorama has its own Animation Timeline just for you!
|
||||||
- Importing PNG and JPEG images, and edit them inside Pixelorama. If you import multiple files, they will be added as individual animation frames.
|
- Import images and edit them inside Pixelorama. If you import multiple files, they will be added as individual animation frames.
|
||||||
- Export your gorgeous art as PNG files.
|
- Export your gorgeous art as PNG files.
|
||||||
- Save snd open your projects as Pixelorama's custom file format, .pxo
|
- Save snd open your projects as Pixelorama's custom file format, .pxo
|
||||||
|
- Undo/Redo support!
|
||||||
|
- Horizontal & vertical mirrored drawing!
|
||||||
- Tile Mode for pattern creation!
|
- Tile Mode for pattern creation!
|
||||||
- Split screen mode to see your masterpiece twice!
|
- Split screen mode to see your masterpiece twice!
|
||||||
- Create straight lines for pencil and eraser by holding down Shift while you draw.
|
- Create straight lines for pencil and eraser by holding down Shift while you draw.
|
||||||
- The middle mouse wheel isn’t forgotten, you can use it to pan around the canvas and by scrolling up and down, you can zoom in and out!
|
- The middle mouse wheel isn’t forgotten, you can use it to pan around the canvas and by scrolling up and down, you can zoom in and out!
|
||||||
- Keyboard shortcuts! I’m pretty sure this is a lifesaver for most of you.
|
- Keyboard shortcuts! I’m pretty sure this is a lifesaver for most of you.
|
||||||
- Just like onions, Pixelorama has a multiple layer system! You can add, remove, move up and down, clone and merge as many layers as you like!
|
- Just like onions, Pixelorama has a multiple layer system! You can add, remove, move up and down, clone and merge as many layers as you like! You can also rename them!
|
||||||
- Scale and crop your images!
|
- Scale, crop and flip your images!
|
||||||
|
|
||||||
Make sure to read my blog post on Function(Overload) for more information! https://functionoverload590613498.wordpress.com/2019/08/18/i-made-my-own-sprite-editor-in-godot/
|
Make sure to read my blog post on Function(Overload) for more information! https://functionoverload590613498.wordpress.com/2019/08/18/i-made-my-own-sprite-editor-in-godot/
|
||||||
|
|
||||||
Download it as a stand-alone on itch.io: https://orama-interactive.itch.io/pixelorama
|
Download it as a stand-alone on itch.io: https://orama-interactive.itch.io/pixelorama
|
||||||
|
|
||||||
And if you like, consider helping me by sponsoring this project!
|
If you like, consider helping me by sponsoring this project! It would enable me to focus more on Pixelorama, and make more projects in the future!
|
||||||
|
|
|
@ -542,8 +542,12 @@ func draw_pixel(pos : Vector2, color : Color, current_mouse_button : String, cur
|
||||||
src_rect.size.y = min(src_rect.size.y, selection_rect.size.y)
|
src_rect.size.y = min(src_rect.size.y, selection_rect.size.y)
|
||||||
|
|
||||||
#Handle mirroring
|
#Handle mirroring
|
||||||
var mirror_x := east_limit + west_limit - pos.x - (pos.x - dst.x) - 1
|
var mirror_x := east_limit + west_limit - pos.x - (pos.x - dst.x)
|
||||||
var mirror_y := south_limit + north_limit - pos.y - (pos.y - dst.y) - 1
|
var mirror_y := south_limit + north_limit - pos.y - (pos.y - dst.y)
|
||||||
|
if int(pos_rect_clipped.size.x) % 2 != 0:
|
||||||
|
mirror_x -= 1
|
||||||
|
if int(pos_rect_clipped.size.y) % 2 != 0:
|
||||||
|
mirror_y -= 1
|
||||||
|
|
||||||
if color.a > 0: #If it's the pencil
|
if color.a > 0: #If it's the pencil
|
||||||
layers[current_layer_index][0].blend_rect(custom_brush_image, src_rect, dst)
|
layers[current_layer_index][0].blend_rect(custom_brush_image, src_rect, dst)
|
||||||
|
|
|
@ -20,6 +20,7 @@ var draw_grid := false
|
||||||
var canvases := []
|
var canvases := []
|
||||||
# warning-ignore:unused_class_variable
|
# warning-ignore:unused_class_variable
|
||||||
var hidden_canvases := []
|
var hidden_canvases := []
|
||||||
|
var control : Node
|
||||||
var canvas : Canvas
|
var canvas : Canvas
|
||||||
var canvas_parent : Node
|
var canvas_parent : Node
|
||||||
var second_viewport : ViewportContainer
|
var second_viewport : ViewportContainer
|
||||||
|
@ -109,6 +110,7 @@ var custom_right_brush_texture := ImageTexture.new()
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
undo_redo = UndoRedo.new()
|
undo_redo = UndoRedo.new()
|
||||||
var root = get_tree().get_root()
|
var root = get_tree().get_root()
|
||||||
|
control = find_node_by_name(root, "Control")
|
||||||
canvas = find_node_by_name(root, "Canvas")
|
canvas = find_node_by_name(root, "Canvas")
|
||||||
canvases.append(canvas)
|
canvases.append(canvas)
|
||||||
canvas_parent = canvas.get_parent()
|
canvas_parent = canvas.get_parent()
|
||||||
|
@ -165,6 +167,12 @@ func find_node_by_name(root, node_name) -> Node:
|
||||||
return found
|
return found
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
func notification_label(text : String) -> void:
|
||||||
|
var notification : Label = load("res://Prefabs/NotificationLabel.tscn").instance()
|
||||||
|
notification.text = text
|
||||||
|
notification.rect_position = Vector2(240, OS.window_size.y - 150)
|
||||||
|
get_tree().get_root().add_child(notification)
|
||||||
|
|
||||||
func undo(_canvases : Array, layer_index : int = -1) -> void:
|
func undo(_canvases : Array, layer_index : int = -1) -> void:
|
||||||
undos -= 1
|
undos -= 1
|
||||||
var action_name := undo_redo.get_current_action_name()
|
var action_name := undo_redo.get_current_action_name()
|
||||||
|
@ -202,7 +210,8 @@ func undo(_canvases : Array, layer_index : int = -1) -> void:
|
||||||
frame_container.move_child(_canvases[0].frame_button, current_frame)
|
frame_container.move_child(_canvases[0].frame_button, current_frame)
|
||||||
canvas_parent.move_child(_canvases[0], current_frame)
|
canvas_parent.move_child(_canvases[0], current_frame)
|
||||||
|
|
||||||
print("Undo: ", action_name)
|
notification_label("Undo: %s" % action_name)
|
||||||
|
|
||||||
|
|
||||||
func redo(_canvases : Array, layer_index : int = -1) -> void:
|
func redo(_canvases : Array, layer_index : int = -1) -> void:
|
||||||
if undos < undo_redo.get_version(): #If we did undo and then redo
|
if undos < undo_redo.get_version(): #If we did undo and then redo
|
||||||
|
@ -241,7 +250,8 @@ func redo(_canvases : Array, layer_index : int = -1) -> void:
|
||||||
frame_container.move_child(_canvases[0].frame_button, current_frame)
|
frame_container.move_child(_canvases[0].frame_button, current_frame)
|
||||||
canvas_parent.move_child(_canvases[0], current_frame)
|
canvas_parent.move_child(_canvases[0], current_frame)
|
||||||
|
|
||||||
print("Redo: ", action_name)
|
if control.redone:
|
||||||
|
notification_label("Redo: %s" % action_name)
|
||||||
|
|
||||||
func frame_changed(value : int) -> void:
|
func frame_changed(value : int) -> void:
|
||||||
current_frame = value
|
current_frame = value
|
||||||
|
@ -302,7 +312,7 @@ func undo_custom_brush(_brush_button : Button = null) -> void:
|
||||||
hbox_container.add_child(_brush_button)
|
hbox_container.add_child(_brush_button)
|
||||||
hbox_container.move_child(_brush_button, _brush_button.custom_brush_index - brushes_from_files)
|
hbox_container.move_child(_brush_button, _brush_button.custom_brush_index - brushes_from_files)
|
||||||
_brush_button.get_node("DeleteButton").visible = false
|
_brush_button.get_node("DeleteButton").visible = false
|
||||||
print("Undo: ", action_name)
|
notification_label("Undo: %s" % action_name)
|
||||||
|
|
||||||
func redo_custom_brush(_brush_button : Button = null) -> void:
|
func redo_custom_brush(_brush_button : Button = null) -> void:
|
||||||
if undos < undo_redo.get_version(): #If we did undo and then redo
|
if undos < undo_redo.get_version(): #If we did undo and then redo
|
||||||
|
@ -311,7 +321,8 @@ func redo_custom_brush(_brush_button : Button = null) -> void:
|
||||||
var hbox_container := find_node_by_name(get_tree().get_root(), "CustomBrushHBoxContainer")
|
var hbox_container := find_node_by_name(get_tree().get_root(), "CustomBrushHBoxContainer")
|
||||||
if action_name == "Delete Custom Brush":
|
if action_name == "Delete Custom Brush":
|
||||||
hbox_container.remove_child(_brush_button)
|
hbox_container.remove_child(_brush_button)
|
||||||
print("Redo: ", action_name)
|
if control.redone:
|
||||||
|
notification_label("Redo: %s" % action_name)
|
||||||
|
|
||||||
func update_left_custom_brush() -> void:
|
func update_left_custom_brush() -> void:
|
||||||
if custom_left_brush_index > -1:
|
if custom_left_brush_index > -1:
|
||||||
|
|
|
@ -9,6 +9,7 @@ var import_as_new_frame : CheckBox
|
||||||
var export_all_frames : CheckBox
|
var export_all_frames : CheckBox
|
||||||
var export_as_single_file : CheckBox
|
var export_as_single_file : CheckBox
|
||||||
var export_vertical_spritesheet : CheckBox
|
var export_vertical_spritesheet : CheckBox
|
||||||
|
var redone := false
|
||||||
var fps := 1.0
|
var fps := 1.0
|
||||||
var animation_loop := 0 #0 is no loop, 1 is cycle loop, 2 is ping-pong loop
|
var animation_loop := 0 #0 is no loop, 1 is cycle loop, 2 is ping-pong loop
|
||||||
var animation_forward := true
|
var animation_forward := true
|
||||||
|
@ -168,7 +169,9 @@ func edit_menu_id_pressed(id : int) -> void:
|
||||||
0: #Undo
|
0: #Undo
|
||||||
Global.undo_redo.undo()
|
Global.undo_redo.undo()
|
||||||
1: #Redo
|
1: #Redo
|
||||||
|
redone = true
|
||||||
Global.undo_redo.redo()
|
Global.undo_redo.redo()
|
||||||
|
redone = false
|
||||||
2: #Scale Image
|
2: #Scale Image
|
||||||
$ScaleImage.popup_centered()
|
$ScaleImage.popup_centered()
|
||||||
Global.can_draw = false
|
Global.can_draw = false
|
||||||
|
@ -382,6 +385,7 @@ func _on_SaveSprite_file_selected(path) -> void:
|
||||||
file.store_buffer(brush.get_data())
|
file.store_buffer(brush.get_data())
|
||||||
file.store_line("END_BRUSHES")
|
file.store_line("END_BRUSHES")
|
||||||
file.close()
|
file.close()
|
||||||
|
Global.notification_label("File saved")
|
||||||
|
|
||||||
func _on_ImportSprites_files_selected(paths) -> void:
|
func _on_ImportSprites_files_selected(paths) -> void:
|
||||||
if !import_as_new_frame.pressed: #If we're not adding a new frame, delete the previous
|
if !import_as_new_frame.pressed: #If we're not adding a new frame, delete the previous
|
||||||
|
@ -442,6 +446,8 @@ func clear_canvases() -> void:
|
||||||
if child is Canvas:
|
if child is Canvas:
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
Global.canvases.clear()
|
Global.canvases.clear()
|
||||||
|
current_save_path = ""
|
||||||
|
current_export_path = ""
|
||||||
|
|
||||||
func _on_ExportSprites_file_selected(path : String) -> void:
|
func _on_ExportSprites_file_selected(path : String) -> void:
|
||||||
current_export_path = path
|
current_export_path = path
|
||||||
|
@ -461,6 +467,7 @@ func export_project() -> void:
|
||||||
save_spritesheet()
|
save_spritesheet()
|
||||||
else:
|
else:
|
||||||
save_sprite(Global.canvas, current_export_path)
|
save_sprite(Global.canvas, current_export_path)
|
||||||
|
Global.notification_label("File exported")
|
||||||
|
|
||||||
func save_sprite(canvas : Canvas, path : String) -> void:
|
func save_sprite(canvas : Canvas, path : String) -> void:
|
||||||
var whole_image := Image.new()
|
var whole_image := Image.new()
|
||||||
|
|
15
Scripts/NotificationLabel.gd
Normal file
15
Scripts/NotificationLabel.gd
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
extends Label
|
||||||
|
|
||||||
|
# Declare member variables here. Examples:
|
||||||
|
# var a = 2
|
||||||
|
# var b = "text"
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
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()
|
BIN
icon.png
BIN
icon.png
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.3 KiB |
|
@ -29,7 +29,7 @@ _global_script_class_icons={
|
||||||
config/name="Pixelorama"
|
config/name="Pixelorama"
|
||||||
run/main_scene="res://Main.tscn"
|
run/main_scene="res://Main.tscn"
|
||||||
config/icon="res://icon.png"
|
config/icon="res://icon.png"
|
||||||
config/Version="v0.3"
|
config/Version="v0.4"
|
||||||
|
|
||||||
[autoload]
|
[autoload]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue