diff --git a/Main.tscn b/Main.tscn index 695a7f329..4f0e291a3 100644 --- a/Main.tscn +++ b/Main.tscn @@ -23,6 +23,7 @@ size_flags_horizontal = 3 custom_constants/separation = 0 [node name="ToolPanel" type="Panel" parent="UI"] +editor/display_folded = true margin_right = 230.0 margin_bottom = 600.0 rect_min_size = Vector2( 230, 0 ) @@ -36,6 +37,7 @@ size_flags_horizontal = 3 size_flags_vertical = 3 [node name="MenusAndTools" type="VBoxContainer" parent="UI/ToolPanel/Tools"] +editor/display_folded = true margin_right = 230.0 margin_bottom = 224.0 size_flags_vertical = 3 @@ -167,6 +169,7 @@ margin_right = 230.0 margin_bottom = 232.0 [node name="ToolOptions" type="HBoxContainer" parent="UI/ToolPanel/Tools"] +editor/display_folded = true margin_top = 236.0 margin_right = 230.0 margin_bottom = 460.0 @@ -174,7 +177,6 @@ size_flags_vertical = 3 custom_constants/separation = 0 [node name="LeftToolOptions" type="VBoxContainer" parent="UI/ToolPanel/Tools/ToolOptions"] -editor/display_folded = true margin_right = 113.0 margin_bottom = 224.0 size_flags_horizontal = 3 @@ -260,12 +262,14 @@ text = "C" margin_top = 166.0 margin_right = 113.0 margin_bottom = 190.0 +hint_tooltip = "Enable horizontal mirrored drawing for the left tool" text = "Horiz. Mirror" [node name="LeftVerticalMirroring" type="CheckBox" parent="UI/ToolPanel/Tools/ToolOptions/LeftToolOptions"] margin_top = 194.0 margin_right = 113.0 margin_bottom = 218.0 +hint_tooltip = "Enable vertical mirrored drawing for the left tool" text = "Vert. Mirror" [node name="VSeparator" type="VSeparator" parent="UI/ToolPanel/Tools/ToolOptions"] @@ -274,7 +278,6 @@ margin_right = 117.0 margin_bottom = 224.0 [node name="RightToolOptions" type="VBoxContainer" parent="UI/ToolPanel/Tools/ToolOptions"] -editor/display_folded = true margin_left = 117.0 margin_right = 230.0 margin_bottom = 224.0 @@ -360,12 +363,14 @@ text = "C" margin_top = 166.0 margin_right = 113.0 margin_bottom = 190.0 +hint_tooltip = "Enable horizontal mirrored drawing for the right tool" text = "Horiz. Mirror" [node name="RightVerticalMirroring" type="CheckBox" parent="UI/ToolPanel/Tools/ToolOptions/RightToolOptions"] margin_top = 194.0 margin_right = 113.0 margin_bottom = 218.0 +hint_tooltip = "Enable vertical mirrored drawing for the right tool" text = "Vert. Mirror" [node name="HSeparator2" type="HSeparator" parent="UI/ToolPanel/Tools"] diff --git a/Prefabs/NotificationLabel.tscn b/Prefabs/NotificationLabel.tscn new file mode 100644 index 000000000..fb97a8c32 --- /dev/null +++ b/Prefabs/NotificationLabel.tscn @@ -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"] diff --git a/README.md b/README.md index 9d76243ba..e9a6e6823 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,30 @@ # 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. -- 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. - 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. - Save snd open your projects as Pixelorama's custom file format, .pxo +- Undo/Redo support! +- Horizontal & vertical mirrored drawing! - Tile Mode for pattern creation! - Split screen mode to see your masterpiece twice! - 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! - 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! -- Scale and crop your images! +- 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, 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/ 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! diff --git a/Scripts/Canvas.gd b/Scripts/Canvas.gd index 8bc4590f7..e0346f464 100644 --- a/Scripts/Canvas.gd +++ b/Scripts/Canvas.gd @@ -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) #Handle mirroring - var mirror_x := east_limit + west_limit - pos.x - (pos.x - dst.x) - 1 - var mirror_y := south_limit + north_limit - pos.y - (pos.y - dst.y) - 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) + 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 layers[current_layer_index][0].blend_rect(custom_brush_image, src_rect, dst) diff --git a/Scripts/Global.gd b/Scripts/Global.gd index 0d46d1937..254c102f6 100644 --- a/Scripts/Global.gd +++ b/Scripts/Global.gd @@ -20,6 +20,7 @@ var draw_grid := false var canvases := [] # warning-ignore:unused_class_variable var hidden_canvases := [] +var control : Node var canvas : Canvas var canvas_parent : Node var second_viewport : ViewportContainer @@ -109,6 +110,7 @@ var custom_right_brush_texture := ImageTexture.new() func _ready() -> void: undo_redo = UndoRedo.new() var root = get_tree().get_root() + control = find_node_by_name(root, "Control") canvas = find_node_by_name(root, "Canvas") canvases.append(canvas) canvas_parent = canvas.get_parent() @@ -165,6 +167,12 @@ func find_node_by_name(root, node_name) -> Node: return found 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: undos -= 1 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) 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: 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) 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: current_frame = value @@ -302,7 +312,7 @@ func undo_custom_brush(_brush_button : Button = null) -> void: hbox_container.add_child(_brush_button) hbox_container.move_child(_brush_button, _brush_button.custom_brush_index - brushes_from_files) _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: 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") if action_name == "Delete Custom Brush": 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: if custom_left_brush_index > -1: diff --git a/Scripts/Main.gd b/Scripts/Main.gd index 81cc8f62e..1b434a901 100644 --- a/Scripts/Main.gd +++ b/Scripts/Main.gd @@ -9,6 +9,7 @@ var import_as_new_frame : CheckBox var export_all_frames : CheckBox var export_as_single_file : CheckBox var export_vertical_spritesheet : CheckBox +var redone := false var fps := 1.0 var animation_loop := 0 #0 is no loop, 1 is cycle loop, 2 is ping-pong loop var animation_forward := true @@ -168,7 +169,9 @@ func edit_menu_id_pressed(id : int) -> void: 0: #Undo Global.undo_redo.undo() 1: #Redo + redone = true Global.undo_redo.redo() + redone = false 2: #Scale Image $ScaleImage.popup_centered() Global.can_draw = false @@ -382,6 +385,7 @@ func _on_SaveSprite_file_selected(path) -> void: file.store_buffer(brush.get_data()) file.store_line("END_BRUSHES") file.close() + Global.notification_label("File saved") func _on_ImportSprites_files_selected(paths) -> void: 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: child.queue_free() Global.canvases.clear() + current_save_path = "" + current_export_path = "" func _on_ExportSprites_file_selected(path : String) -> void: current_export_path = path @@ -461,6 +467,7 @@ func export_project() -> void: save_spritesheet() else: save_sprite(Global.canvas, current_export_path) + Global.notification_label("File exported") func save_sprite(canvas : Canvas, path : String) -> void: var whole_image := Image.new() diff --git a/Scripts/NotificationLabel.gd b/Scripts/NotificationLabel.gd new file mode 100644 index 000000000..7db0c4984 --- /dev/null +++ b/Scripts/NotificationLabel.gd @@ -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() diff --git a/icon.png b/icon.png index 2b658158b..916277470 100644 Binary files a/icon.png and b/icon.png differ diff --git a/project.godot b/project.godot index d4c173d9f..ebda5c73e 100644 --- a/project.godot +++ b/project.godot @@ -29,7 +29,7 @@ _global_script_class_icons={ config/name="Pixelorama" run/main_scene="res://Main.tscn" config/icon="res://icon.png" -config/Version="v0.3" +config/Version="v0.4" [autoload]