From 27cb0d2d2fdf73a958457d5e3c5c2d82c1678944 Mon Sep 17 00:00:00 2001 From: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> Date: Sat, 17 Apr 2021 11:30:12 -0700 Subject: [PATCH] New selection system (#474) * Basic move tool * Added marching ants effect on the selection borders * Rename SelectionRectangle to SelectionShape, make it have non-rectangular shape and multiple SelectionShapes can exist - Create multiple selection rectangles - Merge them together if they intersect - Move the selections (without contents as of right now) - Gizmos are being drawn but they are not functional yet Code is very ugly. * Sort vectors counter-clockwise to be used as polygon borders I did this, no idea if it works properly, probably won't be used but I thought I'd keep it saved somewhere * More experiments I may or may not need Trying to generate a polygon from the individual selected pixels * Change default rectangle select behavior and ability to clip polygons using Control * Fix rectangle selection clipping * Split polygon into two with selection subtracting * Move selection with contents with the move tool Code is still a mess, don't bother looking. * Move some methods from SelectionShape.gd to Selection.gd The purpose of this is to generalize some selection code, so that it applies to all polygons, the entire selection. More will follow. * UndoRedo for border moving Nothing else in the selections system works properly in UndoRedo right now. Needs: - UR support for creating selections - UR support for modifying selections (merging and cutting selections together) - UR support for removing selection - UR support for moving content & for all the rest of the remaining features * Moving all of the selection shape logic to Selection.gd Handle all of the polygons there instead of having them as individual nodes. Should be easier to handle undo/redo this way. This commit probably breaks move tool + selection tool and undo/redo. Code is still a mess. For your sanity, I hope you are not reading this. I promise I will clean up. * Move tool works again Buggy and messy, of course. * Remove unneeded code and restore selection move undoredo logic * Made Selection.gd have one big preview_image for when moving content, instead of each polygon having its own image Could be further optimized for some specific cases. We could also remove selected_pixels from SelectionPolygon. * UndoRedo support for creating, deleting, merging and clipping selections UndoRedo support for moving content not added in this commit. Should work but needs more testing. This PR also removes selected_pixels from the SelectionPolygon class. * Confirm & cancel selection movement, should support undoredo properly too Press Enter or do any editing to confirm movement, Escape to cancel. I will most likely add UI buttons for confirm and cancel too. * Mirror View affects selection * Restore Cut, Copy, Paste and Clear Selection Pasting now no longer requires a pre-existing selection and instead copies the selections themselves too. * Created a new Select menu, which has Select All and Clear Selection as options Clear Selection now also confirms content moving. TopMenuContainer code has changed to no longer rely on Global for the menu buttons. * Draw gizmos as rectangles No functionality yet. They may need to be turned to nodes, so that they can easily resize based on zoom level and check for mouse enter/exit events. * Made gizmos get drawn in the sides and corners of the big bounding rectangle instead of individual selection parts Still no functionality yet. * Restore label text * Minor optimization when clipping selections This will execute the for loop less times * Made a Gizmo class, cursor change on hover, has_focus = false on mouse click Now I should actually make them resize when dragged, aye? * Very basic gizmo resizing, still a WIP, does not work properly yet * Start replacing the array of selected pixels with a BitMap This should optimize the selection making a lot, and it also allows for easy border drawing without having to deal with polygons, thanks to the MarchingAntsOutline.shader Still commit is still a WIP, image effects and brushes may not work properly yet. Because the BitMap has a fixed size, the size of the project, moving the selection outside of canvas boundaries has proven to be a bit tricky. I did implement a hacky way of handling it, but it may be buggy and problematic. I'm still unsure whether this is the best way to handle the situation. * Selection works with mirror view * Draw a black rectangle when the user is making a rectangular selection After they release the mouse, the black rectangle becomes the selection * Make Selection.gd update when undoing/redoing * Fix brushes not working properly with non-rectangular selections * Added invert selection * Cache has_selection as a variable for a speedup * Fix conflict issues with the shape tools * Made the bitmap image squared so the marching ants effect will be the same on both dimensions There may be a better way to fix the issue, perhaps inside the shader itself. * Some optimizations to call selection_bitmap_changed() less times * Restored almost all of the image effects Left to do: - Change gradient's behavior. Unsure of how it will work with non-rectangular selections yet, but it should not generate pixels outside of the selection. - Restore rotation - Resize bitmap on image resize - Remove the `pixels` array from the ImageEffect * Fix Selection.gd not updating when changing project * Resize the selection bitmap along with image resize * Restored rotation's old behavior and finally got rid of the selected_pixels array The rotation does not yet work properly with selections, but at least it now "works". * Resize selection too when using gizmos Left to do for gizmos: - Proper cancel transformation - Begin transformation (currently named move_content_start but it should be renamed to something more general) when resizing gizmos - Keep the original image and selection in memory and resize them. Meaning, gizmos should not resize the already resized data, but only resize the original. This is less destructive as there is no danger of data loss. - Always resize on InputEventMouseMotion. This is going to be worse for performance, but it will look better for the user. * Image and bitmap resizing now uses the original data and begin transformation on gizmo click No matter how many times the user resizes on the current transformation, the original data will not be lost until they either confirm or cancel, so there is no data loss before confirmation/cancel. * Cancel transformation now works properly when the selection has been resized * Made gizmos resize on mouse motion, fix issues with negative bounding rectangle and when combined with the move tool * Resizing can now get out of positive bounds, clearing and inverting now gets limited to the canvas bounds Resizing currently does not work properly with negative (left & up) canvas boundaries * Flip image when resizing and the bounding rectangle gets flipped * Call move_content_confirm() when inverting selection * Attempt to implement selection resizing that goes outside of the canvas boundaries (not working properly yet) * Flip selection when resizing to negative bounding rectangle sizes And fix preview_image vertical flipping * Fix rotation so that it works (almost) properly with selections Rotation algorithms now accept and only work with a given image, and the pivot has been added as a parameter * Experimental gizmo rotation - does not work properly yet Transforming the selection outside of the canvas is still broken. * Fix some issues with moving selection out of canvas bounds * Fix more issues with selection getting resized outside of canvas bounds * Update marching ants effect properly when switching between projects And make sure the frequency of the marching ants effect always looks roughly the same on all project sizes * Made the rotation gizmo part of the gizmos array and resize them based on camera zoom * Remove unneeded parameter from move_bitmap_values() * Remove more unneeded parameters * Move the selection only if the cursor is above it and neither shift nor control are currently pressed * Gradient generation now works on non-rectangular selections Although this behavior might not be the intended one * Copy/paste marching ants effect offset Useful for when the selection is in negative coords * Fix issue with clear selection & UndoRedo * Restore the ability to move selection when it's in negative coords * Made the marching ants offset a Project variable This fixes the issue of project switching and keeping the previous project's offset. Again, this is only relevant for when the selection is in negative coords. * Made the "from current selection" palette preset work with the new selection system * Fix out of bounds error when using the rectangular select tool on negative coords * Some code cleanup * Comment out the rotation gizmo for now, since it does not work properly * Update marching ants shader params and gizmo sizes when the bitmap changes * Move some methods around in Selection.gd --- project.godot | 20 + src/Autoload/DrawingAlgos.gd | 627 +++++++++--------- src/Autoload/Export.gd | 2 +- src/Autoload/Global.gd | 47 +- src/Autoload/OpenSave.gd | 6 +- src/Autoload/Palettes.gd | 7 +- src/Autoload/Tools.gd | 1 + src/Classes/Drawers.gd | 14 +- src/Classes/ImageEffect.gd | 33 +- src/Classes/Project.gd | 208 +++++- src/Main.gd | 23 +- src/SelectionRectangle.gd | 192 ------ src/Shaders/MarchingAntsOutline.shader | 52 ++ src/Tools/BaseTool.gd | 7 +- src/Tools/Bucket.gd | 43 +- src/Tools/Draw.gd | 64 +- src/Tools/Eraser.gd | 1 + src/Tools/LightenDarken.gd | 1 + src/Tools/Move.gd | 40 ++ src/Tools/Move.tscn | 23 + src/Tools/Pencil.gd | 1 + src/Tools/RectSelect.gd | 61 +- src/UI/Canvas/CameraMovement.gd | 3 + src/UI/Canvas/Canvas.gd | 11 +- src/UI/Canvas/Canvas.tscn | 20 +- src/UI/Canvas/Selection.gd | 498 ++++++++++++++ src/UI/Dialogs/ExportDialog.gd | 1 + .../Dialogs/ImageEffects/DesaturateDialog.gd | 4 +- .../Dialogs/ImageEffects/FlipImageDialog.gd | 25 +- src/UI/Dialogs/ImageEffects/GradientDialog.gd | 4 +- src/UI/Dialogs/ImageEffects/HSVDialog.gd | 4 +- .../ImageEffects/InvertColorsDialog.gd | 4 +- src/UI/Dialogs/ImageEffects/OutlineDialog.gd | 4 +- src/UI/Dialogs/ImageEffects/ResizeCanvas.gd | 1 + src/UI/Dialogs/ImageEffects/RotateImage.gd | 31 +- src/UI/Dialogs/ImageEffects/ScaleImage.gd | 1 + src/UI/ToolButtons.gd | 1 + src/UI/TopMenuContainer.gd | 80 ++- src/UI/TopMenuContainer.tscn | 22 +- src/UI/UI.tscn | 65 +- 40 files changed, 1468 insertions(+), 784 deletions(-) delete mode 100644 src/SelectionRectangle.gd create mode 100644 src/Shaders/MarchingAntsOutline.shader create mode 100644 src/Tools/Move.gd create mode 100644 src/Tools/Move.tscn create mode 100644 src/UI/Canvas/Selection.gd diff --git a/project.godot b/project.godot index bd4662a82..e3d60239b 100644 --- a/project.godot +++ b/project.godot @@ -488,6 +488,26 @@ right_ellipsetool_tool={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":67,"unicode":0,"echo":false,"script":null) ] } +left_move_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) + ] +} +right_move_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":84,"unicode":0,"echo":false,"script":null) + ] +} +select_all={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null) + ] +} +invert_selection={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":true,"meta":false,"command":true,"pressed":false,"scancode":73,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 4a8ace066..6e62a92fe 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -5,7 +5,7 @@ enum GradientDirection {TOP, BOTTOM, LEFT, RIGHT} func scale3X(sprite : Image, tol : float = 50) -> Image: - var scaled = Image.new() + var scaled := Image.new() scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) scaled.lock() sprite.lock() @@ -62,163 +62,142 @@ func scale3X(sprite : Image, tol : float = 50) -> Image: return scaled -func rotxel(sprite : Image, angle : float, pixels : Array) -> void: +func rotxel(sprite : Image, angle : float, pivot : Vector2) -> void: # If angle is simple, then nn rotation is the best if angle == 0 || angle == PI/2 || angle == PI || angle == 2*PI: - nn_rotate(sprite, angle, pixels) + nn_rotate(sprite, angle, pivot) return var aux : Image = Image.new() aux.copy_from(sprite) - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) - var center : Vector2 = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) var ox : int var oy : int var p : Color aux.lock() sprite.lock() - for pix in pixels: - var x = pix.x - var y = pix.y - var dx = 3*(x - center.x) - var dy = 3*(y - center.y) - var found_pixel : bool = false - for k in range(9): - var i = -1 + k % 3 + for x in sprite.get_size().x: + for y in sprite.get_size().y: + var dx = 3*(x - pivot.x) + var dy = 3*(y - pivot.y) + var found_pixel : bool = false + for k in range(9): + var i = -1 + k % 3 # warning-ignore:integer_division - var j = -1 + int(k / 3) - var dir = atan2(dy + j, dx + i) - var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) - dir -= angle - ox = round(center.x*3 + 1 + mag*cos(dir)) - oy = round(center.y*3 + 1 + mag*sin(dir)) + var j = -1 + int(k / 3) + var dir = atan2(dy + j, dx + i) + var mag = sqrt(pow(dx + i, 2) + pow(dy + j, 2)) + dir -= angle + ox = round(pivot.x*3 + 1 + mag*cos(dir)) + oy = round(pivot.y*3 + 1 + mag*sin(dir)) - if (sprite.get_width() % 2 != 0): - ox += 1 - oy += 1 + if (sprite.get_width() % 2 != 0): + ox += 1 + oy += 1 - if (ox >= 0 && ox < sprite.get_width()*3 - && oy >= 0 && oy < sprite.get_height()*3): - found_pixel = true - break + if (ox >= 0 && ox < sprite.get_width()*3 + && oy >= 0 && oy < sprite.get_height()*3): + found_pixel = true + break - if !found_pixel: - sprite.set_pixel(x, y, Color(0,0,0,0)) - continue + if !found_pixel: + sprite.set_pixel(x, y, Color(0,0,0,0)) + continue - var fil : int = oy % 3 - var col : int = ox % 3 - var index : int = col + 3*fil + var fil : int = oy % 3 + var col : int = ox % 3 + var index : int = col + 3*fil - ox = round((ox - 1)/3.0); - oy = round((oy - 1)/3.0); - var a : Color - var b : Color - var c : Color - var d : Color - var e : Color - var f : Color - var g : Color - var h : Color - var i : Color - if (ox == 0 || ox == sprite.get_width() - 1 || - oy == 0 || oy == sprite.get_height() - 1): - p = aux.get_pixel(ox, oy) - else: - a = aux.get_pixel(ox-1,oy-1); - b = aux.get_pixel(ox,oy-1); - c = aux.get_pixel(ox+1,oy-1); - d = aux.get_pixel(ox-1,oy); - e = aux.get_pixel(ox,oy); - f = aux.get_pixel(ox+1,oy); - g = aux.get_pixel(ox-1,oy+1); - h = aux.get_pixel(ox,oy+1); - i = aux.get_pixel(ox+1,oy+1); + ox = round((ox - 1)/3.0); + oy = round((oy - 1)/3.0); + var a : Color + var b : Color + var c : Color + var d : Color + var e : Color + var f : Color + var g : Color + var h : Color + var i : Color + if (ox == 0 || ox == sprite.get_width() - 1 || + oy == 0 || oy == sprite.get_height() - 1): + p = aux.get_pixel(ox, oy) + else: + a = aux.get_pixel(ox-1,oy-1); + b = aux.get_pixel(ox,oy-1); + c = aux.get_pixel(ox+1,oy-1); + d = aux.get_pixel(ox-1,oy); + e = aux.get_pixel(ox,oy); + f = aux.get_pixel(ox+1,oy); + g = aux.get_pixel(ox-1,oy+1); + h = aux.get_pixel(ox,oy+1); + i = aux.get_pixel(ox+1,oy+1); - match(index): - 0: - p = d if (similarColors(d,b) && !similarColors(d,h) - && !similarColors(b,f)) else e; - 1: - p = b if ((similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,c)) || - (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,a))) else e; - 2: - p = f if (similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h)) else e; - 3: - p = d if ((similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,a)) || - (similarColors(d,b) && !similarColors(d,h) && - !similarColors(b,f) && !similarColors(e,g))) else e; - 4: - p = e - 5: - p = f if((similarColors(b,f) && !similarColors(d,b) && - !similarColors(f,h) && !similarColors(e,i)) - || (similarColors(f,h) && !similarColors(b,f) && - !similarColors(d,h) && !similarColors(e,c))) else e; - 6: - p = d if (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b)) else e; - 7: - p = h if ((similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h) && !similarColors(e,g)) - || (similarColors(d,h) && !similarColors(f,h) && - !similarColors(d,b) && !similarColors(e,i))) else e; - 8: - p = f if (similarColors(f,h) && !similarColors(f,b) && - !similarColors(d,h)) else e; - sprite.set_pixel(x, y, p) + match(index): + 0: + p = d if (similarColors(d,b) && !similarColors(d,h) + && !similarColors(b,f)) else e; + 1: + p = b if ((similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,c)) || + (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,a))) else e; + 2: + p = f if (similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h)) else e; + 3: + p = d if ((similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,a)) || + (similarColors(d,b) && !similarColors(d,h) && + !similarColors(b,f) && !similarColors(e,g))) else e; + 4: + p = e + 5: + p = f if((similarColors(b,f) && !similarColors(d,b) && + !similarColors(f,h) && !similarColors(e,i)) + || (similarColors(f,h) && !similarColors(b,f) && + !similarColors(d,h) && !similarColors(e,c))) else e; + 6: + p = d if (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b)) else e; + 7: + p = h if ((similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h) && !similarColors(e,g)) + || (similarColors(d,h) && !similarColors(f,h) && + !similarColors(d,b) && !similarColors(e,i))) else e; + 8: + p = f if (similarColors(f,h) && !similarColors(f,b) && + !similarColors(d,h)) else e; + sprite.set_pixel(x, y, p) sprite.unlock() aux.unlock() -func fake_rotsprite(sprite : Image, angle : float, pixels : Array) -> void: - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) +func fake_rotsprite(sprite : Image, angle : float, pivot : Vector2) -> void: var selected_sprite := Image.new() - selected_sprite = sprite.get_rect(selection_rectangle) + selected_sprite.copy_from(sprite) selected_sprite.copy_from(scale3X(selected_sprite)) - nn_rotate(selected_sprite, angle, []) + nn_rotate(selected_sprite, angle, pivot * 3) # warning-ignore:integer_division # warning-ignore:integer_division selected_sprite.resize(selected_sprite.get_width() / 3, selected_sprite.get_height() / 3, 0) - sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), selection_rectangle.position) + sprite.blit_rect(selected_sprite, Rect2(Vector2.ZERO, selected_sprite.get_size()), Vector2.ZERO) -func nn_rotate(sprite : Image, angle : float, pixels : Array) -> void: +func nn_rotate(sprite : Image, angle : float, pivot : Vector2) -> void: var aux : Image = Image.new() aux.copy_from(sprite) sprite.lock() aux.lock() var ox: int var oy: int - var center : Vector2 - if pixels: - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) - center = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) - for pix in pixels: - var x = pix.x - var y = pix.y - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y + for x in range(sprite.get_width()): + for y in range(sprite.get_height()): + ox = (x - pivot.x)*cos(angle) + (y - pivot.y)*sin(angle) + pivot.x + oy = -(x - pivot.x)*sin(angle) + (y - pivot.y)*cos(angle) + pivot.y if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) else: sprite.set_pixel(x, y, Color(0,0,0,0)) - else: -# warning-ignore:integer_division -# warning-ignore:integer_division - center = Vector2(sprite.get_width() / 2, sprite.get_height() / 2) - for x in range(sprite.get_width()): - for y in range(sprite.get_height()): - ox = (x - center.x)*cos(angle) + (y - center.y)*sin(angle) + center.x - oy = -(x - center.x)*sin(angle) + (y - center.y)*cos(angle) + center.y - if ox >= 0 && ox < sprite.get_width() && oy >= 0 && oy < sprite.get_height(): - sprite.set_pixel(x, y, aux.get_pixel(ox, oy)) - else: - sprite.set_pixel(x, y, Color(0,0,0,0)) sprite.unlock() aux.unlock() @@ -256,6 +235,7 @@ func scale_image(width : int, height : int, interpolation : int) -> void: func centralize() -> void: + Global.canvas.selection.move_content_confirm() # Find used rect of the current frame (across all of the layers) var used_rect := Rect2() for cel in Global.current_project.frames[Global.current_project.current_frame].cels: @@ -277,6 +257,7 @@ func centralize() -> void: func crop_image(image : Image) -> void: + Global.canvas.selection.move_content_confirm() # Use first cel as a starting rectangle var used_rect : Rect2 = image.get_used_rect() @@ -325,82 +306,101 @@ func resize_canvas(width : int, height : int, offset_x : int, offset_y : int) -> func general_do_scale(width : int, height : int) -> void: - var x_ratio = Global.current_project.size.x / width - var y_ratio = Global.current_project.size.y / height - var new_x_symmetry_point = Global.current_project.x_symmetry_point / x_ratio - var new_y_symmetry_point = Global.current_project.y_symmetry_point / y_ratio - var new_x_symmetry_axis_points = Global.current_project.x_symmetry_axis.points - var new_y_symmetry_axis_points = Global.current_project.y_symmetry_axis.points + var project := Global.current_project + var size := Vector2(width, height).floor() + var x_ratio = project.size.x / width + var y_ratio = project.size.y / height + + var bitmap : BitMap + bitmap = project.resize_bitmap(project.selection_bitmap, size) + + var new_x_symmetry_point = project.x_symmetry_point / x_ratio + var new_y_symmetry_point = project.y_symmetry_point / y_ratio + var new_x_symmetry_axis_points = project.x_symmetry_axis.points + var new_y_symmetry_axis_points = project.y_symmetry_axis.points new_x_symmetry_axis_points[0].y /= y_ratio new_x_symmetry_axis_points[1].y /= y_ratio new_y_symmetry_axis_points[0].x /= x_ratio new_y_symmetry_axis_points[1].x /= x_ratio - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Scale") - Global.current_project.undo_redo.add_do_property(Global.current_project, "size", Vector2(width, height).floor()) - Global.current_project.undo_redo.add_do_property(Global.current_project, "x_symmetry_point", new_x_symmetry_point) - Global.current_project.undo_redo.add_do_property(Global.current_project, "y_symmetry_point", new_y_symmetry_point) - Global.current_project.undo_redo.add_do_property(Global.current_project.x_symmetry_axis, "points", new_x_symmetry_axis_points) - Global.current_project.undo_redo.add_do_property(Global.current_project.y_symmetry_axis, "points", new_y_symmetry_axis_points) + project.undos += 1 + project.undo_redo.create_action("Scale") + project.undo_redo.add_do_property(project, "size", size) + project.undo_redo.add_do_property(project, "selection_bitmap", bitmap) + project.undo_redo.add_do_property(project, "x_symmetry_point", new_x_symmetry_point) + project.undo_redo.add_do_property(project, "y_symmetry_point", new_y_symmetry_point) + project.undo_redo.add_do_property(project.x_symmetry_axis, "points", new_x_symmetry_axis_points) + project.undo_redo.add_do_property(project.y_symmetry_axis, "points", new_y_symmetry_axis_points) func general_undo_scale() -> void: - Global.current_project.undo_redo.add_undo_property(Global.current_project, "size", Global.current_project.size) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "x_symmetry_point", Global.current_project.x_symmetry_point) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "y_symmetry_point", Global.current_project.y_symmetry_point) - Global.current_project.undo_redo.add_undo_property(Global.current_project.x_symmetry_axis, "points", Global.current_project.x_symmetry_axis.points) - Global.current_project.undo_redo.add_undo_property(Global.current_project.y_symmetry_axis, "points", Global.current_project.y_symmetry_axis.points) - Global.current_project.undo_redo.add_undo_method(Global, "undo") - Global.current_project.undo_redo.add_do_method(Global, "redo") - Global.current_project.undo_redo.commit_action() + var project := Global.current_project + project.undo_redo.add_undo_property(project, "size", project.size) + project.undo_redo.add_undo_property(project, "selection_bitmap", project.selection_bitmap) + project.undo_redo.add_undo_property(project, "x_symmetry_point", project.x_symmetry_point) + project.undo_redo.add_undo_property(project, "y_symmetry_point", project.y_symmetry_point) + project.undo_redo.add_undo_property(project.x_symmetry_axis, "points", project.x_symmetry_axis.points) + project.undo_redo.add_undo_property(project.y_symmetry_axis, "points", project.y_symmetry_axis.points) + project.undo_redo.add_undo_method(Global, "undo") + project.undo_redo.add_do_method(Global, "redo") + project.undo_redo.commit_action() func general_do_centralize() -> void: - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Centralize") + var project := Global.current_project + project.undos += 1 + project.undo_redo.create_action("Centralize") func general_undo_centralize() -> void: - Global.current_project.undo_redo.add_undo_method(Global, "undo") - Global.current_project.undo_redo.add_do_method(Global, "redo") - Global.current_project.undo_redo.commit_action() + var project := Global.current_project + project.undo_redo.add_undo_method(Global, "undo") + project.undo_redo.add_do_method(Global, "redo") + project.undo_redo.commit_action() -func invert_image_colors(image : Image, pixels : Array, red := true, green := true, blue := true, alpha := false) -> void: +func invert_image_colors(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: image.lock() - for i in pixels: - var px_color := image.get_pixelv(i) - # Manually invert each color channel - if red: - px_color.r = 1.0 - px_color.r - if green: - px_color.g = 1.0 - px_color.g - if blue: - px_color.b = 1.0 - px_color.b - if alpha: - px_color.a = 1.0 - px_color.a - image.set_pixelv(i, px_color) + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var px_color := image.get_pixelv(pos) + # Manually invert each color channel + if red: + px_color.r = 1.0 - px_color.r + if green: + px_color.g = 1.0 - px_color.g + if blue: + px_color.b = 1.0 - px_color.b + if alpha: + px_color.a = 1.0 - px_color.a + image.set_pixelv(pos, px_color) -func desaturate_image(image : Image, pixels : Array, red := true, green := true, blue := true, alpha := false) -> void: +func desaturate_image(image : Image, affect_selection : bool, project : Project, red := true, green := true, blue := true, alpha := false) -> void: image.lock() - for i in pixels: - var px_color := image.get_pixelv(i) - var gray = px_color.v - if red: - px_color.r = gray - if green: - px_color.g = gray - if blue: - px_color.b = gray - if alpha: - px_color.a = gray + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var px_color := image.get_pixelv(pos) + var gray = px_color.v + if red: + px_color.r = gray + if green: + px_color.g = gray + if blue: + px_color.b = gray + if alpha: + px_color.a = gray - image.set_pixelv(i, px_color) + image.set_pixelv(pos, px_color) -func generate_outline(image : Image, pixels : Array, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: +func generate_outline(image : Image, affect_selection : bool, project : Project, outline_color : Color, thickness : int, diagonal : bool, inside_image : bool) -> void: if image.is_invisible(): return var new_image := Image.new() @@ -408,169 +408,175 @@ func generate_outline(image : Image, pixels : Array, outline_color : Color, thic new_image.lock() image.lock() - for pos in pixels: - var current_pixel := image.get_pixelv(pos) - if current_pixel.a == 0: - continue + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + var current_pixel := image.get_pixelv(pos) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + 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.current_project.size.x: - var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: - new_image.set_pixelv(new_pos, outline_color) + 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.current_project.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.current_project.size.x || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.LEFT * (i - 1) + outline_pos = pos + Vector2.RIGHT # Right + if outline_pos.x >= Global.current_project.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.current_project.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.current_project.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.current_project.size.x && new_pos.y < Global.current_project.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.current_project.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.current_project.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.current_project.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.current_project.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.current_project.size.x && outline_pos.y >= Global.current_project.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: + 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.current_project.size.y: + new_pos = pos + Vector2.RIGHT * i # Right + if new_pos.x < Global.current_project.size.x: var new_pixel = image.get_pixelv(new_pos) - if new_pixel.a > 0: + if new_pixel.a == 0: new_image.set_pixelv(new_pos, outline_color) - outline_pos = pos + Vector2.DOWN # Down - if outline_pos.y >= Global.current_project.size.y || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + Vector2.UP * (i - 1) + 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: + 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.current_project.size.x && new_pos.y < Global.current_project.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.DOWN * i # Down + if new_pos.y < Global.current_project.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.current_project.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.current_project.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.current_project.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.current_project.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.current_project.size.x && outline_pos.y >= Global.current_project.size.y) || image.get_pixelv(outline_pos).a == 0: - var new_pos : Vector2 = pos + (Vector2.LEFT + Vector2.UP) * (i - 1) + 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: + 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.LEFT + Vector2.DOWN) * i # Bottom left + if new_pos.x >= 0 && new_pos.y < Global.current_project.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 * i # Right - if new_pos.x < Global.current_project.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.RIGHT + Vector2.UP) * i # Top right + if new_pos.x < Global.current_project.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.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.current_project.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.current_project.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.current_project.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.current_project.size.x && new_pos.y < Global.current_project.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.DOWN) * i # Bottom right + if new_pos.x < Global.current_project.size.x && new_pos.y < Global.current_project.size.y: + var new_pixel = image.get_pixelv(new_pos) + if new_pixel.a == 0: + new_image.set_pixelv(new_pos, outline_color) image.unlock() new_image.unlock() image.copy_from(new_image) -func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, pixels : Array) -> void: +func adjust_hsv(img: Image, delta_h : float, delta_s : float, delta_v : float, affect_selection : bool, project : Project) -> void: img.lock() - for i in pixels: - var c : Color = img.get_pixelv(i) - # Hue - var hue = range_lerp(c.h,0,1,-180,180) - hue = hue + delta_h + for x in project.size.x: + for y in project.size.y: + var pos := Vector2(x, y) + if affect_selection and !project.can_pixel_get_drawn(pos): + continue + var c : Color = img.get_pixelv(pos) + # Hue + var hue = range_lerp(c.h,0,1,-180,180) + hue = hue + delta_h - while(hue >= 180): - hue -= 360 - while(hue < -180): - hue += 360 + while(hue >= 180): + hue -= 360 + while(hue < -180): + hue += 360 - # Saturation - var sat = c.s - if delta_s > 0: - sat = range_lerp(delta_s,0,100,c.s,1) - elif delta_s < 0: - sat = range_lerp(delta_s,-100,0,0,c.s) + # Saturation + var sat = c.s + if delta_s > 0: + sat = range_lerp(delta_s,0,100,c.s,1) + elif delta_s < 0: + sat = range_lerp(delta_s,-100,0,0,c.s) - # Value - var val = c.v - if delta_v > 0: - val = range_lerp(delta_v,0,100,c.v,1) - elif delta_v < 0: - val = range_lerp(delta_v,-100,0,0,c.v) + # Value + var val = c.v + if delta_v > 0: + val = range_lerp(delta_v,0,100,c.v,1) + elif delta_v < 0: + val = range_lerp(delta_v,-100,0,0,c.v) - c.h = range_lerp(hue,-180,180,0,1) - c.s = sat - c.v = val - img.set_pixelv(i,c) - - img.unlock() + c.h = range_lerp(hue,-180,180,0,1) + c.s = sat + c.v = val + img.set_pixelv(pos, c) -func generate_gradient(image : Image, colors : Array, steps := 2, direction : int = GradientDirection.TOP, pixels = Global.current_project.selected_pixels) -> void: +func generate_gradient(image : Image, colors : Array, steps : int, direction : int, affect_selection : bool, project : Project) -> void: if colors.size() < 2: return @@ -584,8 +590,13 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in if direction == GradientDirection.BOTTOM or direction == GradientDirection.RIGHT: colors.invert() - var selection_rectangle := Rect2(pixels[0].x, pixels[0].y, pixels[-1].x - pixels[0].x + 1, pixels[-1].y - pixels[0].y + 1) - var size := selection_rectangle.size + var draw_rectangle := Rect2() + var selection := affect_selection and project.has_selection + if selection: + draw_rectangle = project.get_selection_rectangle() + else: + draw_rectangle = Rect2(Vector2.ZERO, project.size) + var size := draw_rectangle.size image.lock() var gradient_size @@ -596,7 +607,9 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in var start = i * gradient_size var end = (i + 1) * gradient_size for yy in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + pixels[0] + var pos : Vector2 = Vector2(xx, yy) + draw_rectangle.position + if selection and !project.selection_bitmap.get_bit(pos): + continue image.set_pixelv(pos, colors[i]) else: @@ -606,5 +619,7 @@ func generate_gradient(image : Image, colors : Array, steps := 2, direction : in var start = i * gradient_size var end = (i + 1) * gradient_size for xx in range(start, end): - var pos : Vector2 = Vector2(xx, yy) + pixels[0] + var pos : Vector2 = Vector2(xx, yy) + draw_rectangle.position + if selection and !project.selection_bitmap.get_bit(pos): + continue image.set_pixelv(pos, colors[i]) diff --git a/src/Autoload/Export.gd b/src/Autoload/Export.gd index 607895e02..e41884795 100644 --- a/src/Autoload/Export.gd +++ b/src/Autoload/Export.gd @@ -196,7 +196,7 @@ func export_processed_images(ignore_overwrites: bool, export_dialog: AcceptDialo # Store settings for quick export and when the dialog is opened again was_exported = true Global.current_project.was_exported = true - Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format))) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export") + " %s" % (file_name + file_format_string(file_format))) # Only show when not exporting gif - gif export finishes in thread if not (current_tab == ExportTab.ANIMATION and animation_type == AnimationType.ANIMATED): diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index 125a3939e..fbfb781b7 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -114,16 +114,10 @@ var small_preview_viewport : ViewportContainer var camera : Camera2D var camera2 : Camera2D var camera_preview : Camera2D -var selection_rectangle : Polygon2D var horizontal_ruler : BaseButton var vertical_ruler : BaseButton var transparent_checker : ColorRect -var file_menu : MenuButton -var edit_menu : MenuButton -var view_menu : MenuButton -var image_menu : MenuButton -var help_menu : MenuButton var cursor_position_label : Label var zoom_level_label : Label @@ -201,7 +195,7 @@ func _ready() -> void: # XDGDataDirs depends on it nyaa directory_module = XDGDataPaths.new() image_clipboard = Image.new() - Input.set_custom_mouse_cursor(Global.cursor_image, Input.CURSOR_CROSS, Vector2(15, 15)) + Input.set_custom_mouse_cursor(cursor_image, Input.CURSOR_CROSS, Vector2(15, 15)) var root = get_tree().get_root() control = find_node_by_name(root, "Control") @@ -218,16 +212,10 @@ func _ready() -> void: camera = find_node_by_name(main_viewport, "Camera2D") camera2 = find_node_by_name(root, "Camera2D2") camera_preview = find_node_by_name(root, "CameraPreview") - selection_rectangle = find_node_by_name(root, "SelectionRectangle") horizontal_ruler = find_node_by_name(root, "HorizontalRuler") vertical_ruler = find_node_by_name(root, "VerticalRuler") transparent_checker = find_node_by_name(root, "TransparentChecker") - file_menu = find_node_by_name(root, "FileMenu") - edit_menu = find_node_by_name(root, "EditMenu") - view_menu = find_node_by_name(root, "ViewMenu") - image_menu = find_node_by_name(root, "ImageMenu") - help_menu = find_node_by_name(root, "HelpMenu") cursor_position_label = find_node_by_name(root, "CursorPosition") zoom_level_label = find_node_by_name(root, "ZoomLevel") @@ -362,7 +350,7 @@ func general_redo(project : Project = current_project) -> void: func undo(_frame_index := -1, _layer_index := -1, project : Project = current_project) -> void: general_undo(project) var action_name : String = project.undo_redo.get_current_action_name() - if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": + if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Move Selection" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": if _layer_index > -1 and _frame_index > -1: canvas.update_texture(_layer_index, _frame_index, project) else: @@ -370,11 +358,12 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr for j in project.layers.size(): canvas.update_texture(j, i, project) + canvas.selection.update() if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.update() - Global.canvas.pixel_grid.update() - Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] + canvas.grid.update() + canvas.pixel_grid.update() + cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] elif "Frame" in action_name: # This actually means that frames.size is one, but it hasn't been updated yet @@ -396,7 +385,7 @@ func undo(_frame_index := -1, _layer_index := -1, project : Project = current_pr func redo(_frame_index := -1, _layer_index := -1, project : Project = current_project) -> void: general_redo(project) var action_name : String = project.undo_redo.get_current_action_name() - if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": + if action_name == "Draw" or action_name == "Draw Shape" or action_name == "Rectangle Select" or action_name == "Move Selection" or action_name == "Scale" or action_name == "Centralize" or action_name == "Merge Layer" or action_name == "Link Cel" or action_name == "Unlink Cel": if _layer_index > -1 and _frame_index > -1: canvas.update_texture(_layer_index, _frame_index, project) else: @@ -404,11 +393,12 @@ func redo(_frame_index := -1, _layer_index := -1, project : Project = current_pr for j in project.layers.size(): canvas.update_texture(j, i, project) + canvas.selection.update() if action_name == "Scale": canvas.camera_zoom() - Global.canvas.grid.update() - Global.canvas.pixel_grid.update() - Global.cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] + canvas.grid.update() + canvas.pixel_grid.update() + cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y] elif "Frame" in action_name: if project.frames.size() == 1: # Stop animating @@ -432,6 +422,7 @@ func title_changed(value : String) -> void: func project_changed(value : int) -> void: + canvas.selection.move_content_confirm() current_project_index = value current_project = projects[value] current_project.change_project() @@ -477,9 +468,10 @@ func change_button_texturerect(texture_button : TextureRect, new_file_name : Str func update_hint_tooltips() -> void: - var root = get_tree().get_root() + var root = control + var tool_buttons = root.find_node("ToolButtons") - var rect_select : BaseButton = find_node_by_name(root, "RectSelect") + var rect_select : BaseButton = tool_buttons.find_node("RectSelect") rect_select.hint_tooltip = tr("""Rectangular Selection %s for left mouse button @@ -487,6 +479,13 @@ func update_hint_tooltips() -> void: Press %s to move the content""") % [InputMap.get_action_list("left_rectangle_select_tool")[0].as_text(), InputMap.get_action_list("right_rectangle_select_tool")[0].as_text(), "Shift"] + var move_select : BaseButton = tool_buttons.find_node("Move") + move_select.hint_tooltip = tr("""Move + +%s for left mouse button +%s for right mouse button""") % [InputMap.get_action_list("left_move_tool")[0].as_text(), InputMap.get_action_list("right_move_tool")[0].as_text()] + + var zoom_tool : BaseButton = find_node_by_name(root, "Zoom") zoom_tool.hint_tooltip = tr("""Zoom @@ -616,7 +615,7 @@ func save_project_to_recent_list(path : String) -> void: func update_recent_projects_submenu() -> void: - for project in Global.recent_projects: + for project in recent_projects: recent_projects_submenu.add_item(project.get_file()) func use_osx_shortcuts() -> void: diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 9fabf9730..a073c3649 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -119,8 +119,8 @@ func open_pxo_file(path : String, untitled_backup : bool = false, replace_empty new_project.directory_path = Export.directory_path new_project.file_name = Export.file_name Export.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % path.get_file()) - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % path.get_file()) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) Global.save_project_to_recent_list(path) @@ -339,7 +339,7 @@ func save_pxo_file(path : String, autosave : bool, use_zstd_compression := true, Export.directory_path = path.get_base_dir() Export.was_exported = false project.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % path.get_file()) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % path.get_file()) Global.save_project_to_recent_list(path) diff --git a/src/Autoload/Palettes.gd b/src/Autoload/Palettes.gd index d3c986286..30bdca114 100644 --- a/src/Autoload/Palettes.gd +++ b/src/Autoload/Palettes.gd @@ -124,7 +124,12 @@ func create_new_palette_from_current_palette(name: String, comment: String) -> v func create_new_palette_from_current_selection(name: String, comment: String, width: int, height: int, add_alpha_colors: bool, get_colors_from: int): var new_palette: Palette = Palette.new(name, width, height, comment) var current_project = Global.current_project - var pixels = current_project.selected_pixels.duplicate() + var pixels := [] + for x in current_project.size.x: + for y in current_project.size.y: + var pos := Vector2(x, y) + if current_project.selection_bitmap.get_bit(pos): + pixels.append(pos) fill_new_palette_with_colors(pixels, new_palette, add_alpha_colors, get_colors_from) diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd index d503550db..4bd43109a 100644 --- a/src/Autoload/Tools.gd +++ b/src/Autoload/Tools.gd @@ -40,6 +40,7 @@ signal color_changed(color, button) var _tools = { "RectSelect" : "res://src/Tools/RectSelect.tscn", + "Move" : "res://src/Tools/Move.tscn", "Zoom" : "res://src/Tools/Zoom.tscn", "Pan" : "res://src/Tools/Pan.tscn", "ColorPicker" : "res://src/Tools/ColorPicker.tscn", diff --git a/src/Classes/Drawers.gd b/src/Classes/Drawers.gd index 3be9ca9ed..83da1c2e6 100644 --- a/src/Classes/Drawers.gd +++ b/src/Classes/Drawers.gd @@ -75,19 +75,9 @@ func set_pixel(image: Image, position: Vector2, color: Color) -> void: var mirror_y = project.y_symmetry_point - position.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, position.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(position.x, mirror_y)) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y if horizontal_mirror and mirror_x_inside: drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op) diff --git a/src/Classes/ImageEffect.gd b/src/Classes/ImageEffect.gd index 9f4d711b0..58cae2158 100644 --- a/src/Classes/ImageEffect.gd +++ b/src/Classes/ImageEffect.gd @@ -6,7 +6,6 @@ class_name ImageEffect extends AcceptDialog enum {CEL, FRAME, ALL_FRAMES, ALL_PROJECTS} var affect : int = CEL -var pixels := [] var current_cel : Image var current_frame : Image var preview_image : Image @@ -33,6 +32,7 @@ func _ready() -> void: func _about_to_show() -> void: + Global.canvas.selection.move_content_confirm() current_cel = Global.current_project.frames[Global.current_project.current_frame].cels[Global.current_project.current_layer].image current_frame.resize(Global.current_project.size.x, Global.current_project.size.y) current_frame.fill(Color(0, 0, 0, 0)) @@ -48,39 +48,31 @@ func _about_to_show() -> void: func _confirmed() -> void: if affect == CEL: Global.canvas.handle_undo("Draw") - commit_action(current_cel, pixels) + commit_action(current_cel) Global.canvas.handle_redo("Draw") elif affect == FRAME: Global.canvas.handle_undo("Draw", Global.current_project, -1) for cel in Global.current_project.frames[Global.current_project.current_frame].cels: - commit_action(cel.image, pixels) + commit_action(cel.image) Global.canvas.handle_redo("Draw", Global.current_project, -1) elif affect == ALL_FRAMES: Global.canvas.handle_undo("Draw", Global.current_project, -1, -1) for frame in Global.current_project.frames: for cel in frame.cels: - commit_action(cel.image, pixels) + commit_action(cel.image) Global.canvas.handle_redo("Draw", Global.current_project, -1, -1) elif affect == ALL_PROJECTS: for project in Global.projects: - var _pixels := [] - if selection_checkbox.pressed and project.selected_pixels: - _pixels = project.selected_pixels.duplicate() - else: - for x in project.size.x: - for y in project.size.y: - _pixels.append(Vector2(x, y)) - Global.canvas.handle_undo("Draw", project, -1, -1) for frame in project.frames: for cel in frame.cels: - commit_action(cel.image, _pixels, project) + commit_action(cel.image, project) Global.canvas.handle_redo("Draw", project, -1, -1) -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: pass @@ -88,15 +80,7 @@ func set_nodes() -> void: pass -func _on_SelectionCheckBox_toggled(button_pressed : bool) -> void: - pixels.clear() - if button_pressed and Global.current_project.selected_pixels: - pixels = Global.current_project.selected_pixels.duplicate() - else: - for x in Global.current_project.size.x: - for y in Global.current_project.size.y: - pixels.append(Vector2(x, y)) - +func _on_SelectionCheckBox_toggled(_button_pressed : bool) -> void: update_preview() @@ -111,7 +95,8 @@ func update_preview() -> void: preview_image.copy_from(current_cel) _: preview_image.copy_from(current_frame) - commit_action(preview_image, pixels) + commit_action(preview_image) + preview_image.unlock() preview_texture.create_from_image(preview_image, 0) preview.texture = preview_texture diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index f62219cb0..ca0b6b1a4 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -1,7 +1,6 @@ class_name Project extends Reference # A class for project properties. - var name := "" setget name_changed var size : Vector2 setget size_changed var undo_redo : UndoRedo @@ -23,8 +22,10 @@ var y_symmetry_point var x_symmetry_axis : SymmetryGuide var y_symmetry_axis : SymmetryGuide -var selected_pixels := [] -var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect +var selection_bitmap := BitMap.new() +# This is useful for when the selection is outside of the canvas boundaries, on the left and/or above (negative coords) +var selection_offset := Vector2.ZERO setget _selection_offset_changed +var has_selection := false # For every camera (currently there are 3) var cameras_zoom := [Vector2(0.15, 0.15), Vector2(0.15, 0.15), Vector2(0.15, 0.15)] # Array of Vector2 @@ -41,6 +42,7 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> frames = _frames name = _name size = _size + selection_bitmap.create(size) update_tile_mode_rects() undo_redo = UndoRedo.new() @@ -74,20 +76,33 @@ func _init(_frames := [], _name := tr("untitled"), _size := Vector2(64, 64)) -> directory_path = OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP) -func select_all_pixels() -> void: - clear_selection() - for x in size.x: - for y in size.y: - selected_pixels.append(Vector2(x, y)) +func commit_undo() -> void: + if Global.canvas.selection.is_moving_content: + Global.canvas.selection.move_content_cancel() + else: + undo_redo.undo() -func clear_selection() -> void: - selected_pixels.clear() +func commit_redo() -> void: + Global.control.redone = true + undo_redo.redo() + Global.control.redone = false -func _set_selected_rect(value : Rect2) -> void: - selected_rect = value - Global.selection_rectangle.set_rect(value) +func selection_bitmap_changed() -> void: + var image := Image.new() + var image_texture := ImageTexture.new() + has_selection = selection_bitmap.get_true_bit_count() > 0 + if has_selection: + image = bitmap_to_image(selection_bitmap) + image_texture.create_from_image(image, 0) + Global.canvas.selection.marching_ants_outline.texture = image_texture + + +func _selection_offset_changed(value : Vector2) -> void: + selection_offset = value + Global.canvas.selection.marching_ants_outline.offset = selection_offset + Global.canvas.selection.update_on_zoom(Global.camera.zoom.x) func change_project() -> void: @@ -146,9 +161,6 @@ func change_project() -> void: self.animation_tags = animation_tags - # Change the selection rectangle - Global.selection_rectangle.set_rect(selected_rect) - # Change the guides for guide in Global.canvas.get_children(): if guide is Guide: @@ -167,21 +179,12 @@ func change_project() -> void: for brush in brushes: Brushes.add_project_brush(brush) - var cameras = [Global.camera, Global.camera2, Global.camera_preview] - var i := 0 - for camera in cameras: - camera.zoom = cameras_zoom[i] - camera.offset = cameras_offset[i] - i += 1 - Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %" Global.canvas.update() Global.canvas.grid.update() - Global.canvas.pixel_grid.update() Global.transparent_checker._ready() Global.animation_timeline.fps_spinbox.value = fps Global.horizontal_ruler.update() Global.vertical_ruler.update() - Global.preview_zoom_slider.value = -Global.camera_preview.zoom.x Global.cursor_position_label.text = "[%s×%s]" % [size.x, size.y] Global.window_title = "%s - Pixelorama %s" % [name, Global.current_version] @@ -192,9 +195,9 @@ func change_project() -> void: if save_path != "": Global.open_sprites_dialog.current_path = save_path Global.save_sprites_dialog.current_path = save_path - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % save_path.get_file()) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % save_path.get_file()) else: - Global.file_menu.get_popup().set_item_text(4, tr("Save")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save")) Export.directory_path = directory_path Export.file_name = file_name @@ -202,13 +205,27 @@ func change_project() -> void: Export.was_exported = was_exported if !was_exported: - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) else: - Global.file_menu.get_popup().set_item_text(6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export") + " %s" % (file_name + Export.file_format_string(file_format))) for j in Global.TileMode.values(): Global.tile_mode_submenu.set_item_checked(j, j == tile_mode) + # Change selection effect & bounding rectangle + Global.canvas.selection.marching_ants_outline.offset = selection_offset + selection_bitmap_changed() + Global.canvas.selection.big_bounding_rectangle = get_selection_rectangle() + Global.canvas.selection.big_bounding_rectangle.position += selection_offset + Global.canvas.selection.update() + + var i := 0 + for camera in [Global.camera, Global.camera2, Global.camera_preview]: + camera.zoom = cameras_zoom[i] + camera.offset = cameras_offset[i] + camera.zoom_changed() + i += 1 + func serialize() -> Dictionary: var layer_data := [] @@ -363,7 +380,6 @@ func name_changed(value : String) -> void: func size_changed(value : Vector2) -> void: size = value update_tile_mode_rects() - Global.selection_rectangle.set_rect(Global.selection_rectangle.get_rect()) func frames_changed(value : Array) -> void: @@ -582,3 +598,135 @@ func update_tile_mode_rects() -> void: func is_empty() -> bool: return frames.size() == 1 and layers.size() == 1 and frames[0].cels[0].image.is_invisible() and animation_tags.size() == 0 + + +func can_pixel_get_drawn(pixel : Vector2) -> bool: + if pixel.x < 0 or pixel.y < 0 or pixel.x >= size.x or pixel.y >= size.y: + return false + var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position + if selection_position.x < 0: + pixel.x -= selection_position.x + if selection_position.y < 0: + pixel.y -= selection_position.y + if has_selection: + return selection_bitmap.get_bit(pixel) + else: + return true + + +func invert_bitmap(bitmap : BitMap) -> void: + for x in bitmap.get_size().x: + for y in bitmap.get_size().y: + var pos := Vector2(x, y) + bitmap.set_bit(pos, !bitmap.get_bit(pos)) + + +# Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L605 +func resize_bitmap(bitmap : BitMap, new_size : Vector2) -> BitMap: + if new_size == bitmap.get_size(): + return bitmap + var new_bitmap := BitMap.new() + new_bitmap.create(new_size) + var lw = min(bitmap.get_size().x, new_size.x) + var lh = min(bitmap.get_size().y, new_size.y) + for x in lw: + for y in lh: + new_bitmap.set_bit(Vector2(x, y), bitmap.get_bit(Vector2(x, y))) + + return new_bitmap + + +# Unexposed BitMap class function - https://github.com/godotengine/godot/blob/master/scene/resources/bit_map.cpp#L622 +func bitmap_to_image(bitmap : BitMap) -> Image: + var image := Image.new() + var width := bitmap.get_size().x + var height := bitmap.get_size().y + var square_size = max(width, height) + image.create(square_size, square_size, false, Image.FORMAT_LA8) + image.lock() + for x in width: + for y in height: + var pos := Vector2(x, y) + var color = Color(1, 1, 1, 1) if bitmap.get_bit(pos) else Color(0, 0, 0, 0) + image.set_pixelv(pos, color) + image.unlock() + return image + + +func get_selection_rectangle(bitmap : BitMap = selection_bitmap) -> Rect2: + var rect := Rect2(Vector2.ZERO, Vector2.ZERO) + if bitmap.get_true_bit_count() > 0: + var image : Image = bitmap_to_image(bitmap) + rect = image.get_used_rect() + return rect + + +func move_bitmap_values(bitmap : BitMap) -> void: + var selection_node = Global.canvas.selection + var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var selection_end : Vector2 = selection_node.big_bounding_rectangle.end + + var image : Image = bitmap_to_image(bitmap) + var selection_rect := image.get_used_rect() + var smaller_image := image.get_rect(selection_rect) + image.lock() + image.fill(Color(0)) + var dst := selection_position + var x_diff = selection_end.x - size.x + var y_diff = selection_end.y - size.y + var nw = max(size.x, size.x + x_diff) + var nh = max(size.y, size.y + y_diff) + + if selection_position.x < 0: + nw -= selection_position.x + self.selection_offset.x = selection_position.x + dst.x = 0 + else: + self.selection_offset.x = 0 + if selection_position.y < 0: + nh -= selection_position.y + self.selection_offset.y = selection_position.y + dst.y = 0 + else: + self.selection_offset.y = 0 + + if nw <= image.get_size().x: + nw = image.get_size().x + if nh <= image.get_size().y: + nh = image.get_size().y + + image.crop(nw, nh) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, Vector2(nw, nh)), dst) + bitmap.create_from_image_alpha(image) + + +func resize_bitmap_values(bitmap : BitMap, new_size : Vector2, flip_x : bool, flip_y : bool) -> BitMap: + var selection_node = Global.canvas.selection + var selection_position : Vector2 = selection_node.big_bounding_rectangle.position + var dst := selection_position + var new_bitmap_size := size + new_bitmap_size.x = max(size.x, abs(selection_position.x) + new_size.x) + new_bitmap_size.y = max(size.y, abs(selection_position.y) + new_size.y) + var new_bitmap := BitMap.new() + var image : Image = bitmap_to_image(bitmap) + var selection_rect := image.get_used_rect() + var smaller_image := image.get_rect(selection_rect) + if selection_position.x <= 0: + self.selection_offset.x = selection_position.x + dst.x = 0 + if selection_position.y <= 0: + self.selection_offset.y = selection_position.y + dst.y = 0 + image.lock() + image.fill(Color(0)) + smaller_image.resize(new_size.x, new_size.y, Image.INTERPOLATE_NEAREST) + if flip_x: + smaller_image.flip_x() + if flip_y: + smaller_image.flip_y() + if new_bitmap_size != size: + image.crop(new_bitmap_size.x, new_bitmap_size.y) + image.blit_rect(smaller_image, Rect2(Vector2.ZERO, new_bitmap_size), dst) + new_bitmap.create_from_image_alpha(image) + + return new_bitmap diff --git a/src/Main.gd b/src/Main.gd index 28074af4f..91ffd5c48 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -162,24 +162,21 @@ func _input(event : InputEvent) -> void: # The section of code below is reserved for Undo and Redo! Do not place code for Input below, but above. if !event.is_echo(): # Checks if the action is pressed down - if event.is_action_pressed("redo_secondary"): # Done, so that "redo_secondary" hasn't - redone = true # a slight delay before it starts. The "redo" and "undo" action don't have a slight delay, - Global.current_project.undo_redo.redo() # The "redo" and "undo" action don't have a slight delay, - redone = false # because they get called as an accelerator once pressed (TopMenuContainer.gd / Line 152). + if event.is_action_pressed("redo_secondary"): + # Done, so that "redo_secondary" hasn't a slight delay before it starts. + # The "redo" and "undo" action don't have a slight delay, + # because they get called as an accelerator once pressed (TopMenuContainer.gd / Line 152). + Global.current_project.commit_redo() return if event.is_action("redo"): # Ctrl + Y - redone = true - Global.current_project.undo_redo.redo() - redone = false + Global.current_project.commit_redo() if event.is_action("redo_secondary"): # Shift + Ctrl + Z - redone = true - Global.current_project.undo_redo.redo() - redone = false + Global.current_project.commit_redo() if event.is_action("undo") and !event.shift: # Ctrl + Z and check if shift isn't pressed - Global.current_project.undo_redo.undo() # so "undo" isn't accidentaly triggered while using "redo_secondary" + Global.current_project.commit_undo() # so "undo" isn't accidentaly triggered while using "redo_secondary" func setup_application_window_size() -> void: @@ -349,8 +346,8 @@ func _on_BackupConfirmation_confirmed(project_paths : Array, backup_paths : Arra Export.file_name = OpenSave.current_save_paths[0].get_file().trim_suffix(".pxo") Export.directory_path = OpenSave.current_save_paths[0].get_base_dir() Export.was_exported = false - Global.file_menu.get_popup().set_item_text(4, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) - Global.file_menu.get_popup().set_item_text(6, tr("Export")) + Global.top_menu_container.file_menu.set_item_text(4, tr("Save") + " %s" % OpenSave.current_save_paths[0].get_file()) + Global.top_menu_container.file_menu.set_item_text(6, tr("Export")) func _on_BackupConfirmation_delete(project_paths : Array, backup_paths : Array) -> void: diff --git a/src/SelectionRectangle.gd b/src/SelectionRectangle.gd deleted file mode 100644 index 479c4536d..000000000 --- a/src/SelectionRectangle.gd +++ /dev/null @@ -1,192 +0,0 @@ -extends Polygon2D - - -var _selected_rect := Rect2(0, 0, 0, 0) -var _clipped_rect := Rect2(0, 0, 0, 0) -var _move_image := Image.new() -var _move_texture := ImageTexture.new() -var _clear_image := Image.new() -var _move_pixel := false -var _clipboard := Image.new() -var _undo_data := {} - - -func _ready() -> void: - _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) - _clear_image.fill(Color(0, 0, 0, 0)) - - -func _draw() -> void: - if _move_pixel: - draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) - - -func has_point(position : Vector2) -> bool: - return _selected_rect.has_point(position) - - -func get_rect() -> Rect2: - return _selected_rect - - -func set_rect(rect : Rect2) -> void: - _selected_rect = rect - polygon[0] = rect.position - polygon[1] = Vector2(rect.end.x, rect.position.y) - polygon[2] = rect.end - polygon[3] = Vector2(rect.position.x, rect.end.y) - visible = not rect.has_no_area() - - var project : Project = Global.current_project - if rect.has_no_area(): - project.selected_pixels = [] - else: - project.clear_selection() - for x in range(rect.position.x, rect.end.x): - for y in range(rect.position.y, rect.end.y): - if x < 0 or x >= project.size.x: - continue - if y < 0 or y >= project.size.y: - continue - project.selected_pixels.append(Vector2(x, y)) - - -func move_rect(move : Vector2) -> void: - _selected_rect.position += move - _clipped_rect.position += move - set_rect(_selected_rect) - - -func select_rect() -> void: - var undo_data = _get_undo_data(false) - Global.current_project.selected_rect = _selected_rect - commit_undo("Rectangle Select", undo_data) - - -func move_start(move_pixel : bool) -> void: - if not move_pixel: - return - - _undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - - var rect = Rect2(Vector2.ZERO, project.size) - _clipped_rect = rect.clip(_selected_rect) - _move_image = image.get_rect(_clipped_rect) - _move_texture.create_from_image(_move_image, 0) - - var size := _clipped_rect.size - rect = Rect2(Vector2.ZERO, size) - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(_clear_image, rect, _clipped_rect.position) - Global.canvas.update_texture(project.current_layer) - - _move_pixel = true - update() - - -func move_end() -> void: - var undo_data = _undo_data if _move_pixel else _get_undo_data(false) - - if _move_pixel: - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _clipped_rect.size - var rect = Rect2(Vector2.ZERO, size) - image.blit_rect_mask(_move_image, _move_image, rect, _clipped_rect.position) - _move_pixel = false - update() - - Global.current_project.selected_rect = _selected_rect - commit_undo("Rectangle Select", undo_data) - _undo_data.clear() - - -func copy() -> void: - if _selected_rect.has_no_area(): - return - - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - _clipboard = image.get_rect(_selected_rect) - if _clipboard.is_invisible(): - return - var brush = _clipboard.get_rect(_clipboard.get_used_rect()) - project.brushes.append(brush) - Brushes.add_project_brush(brush) - -func cut() -> void: # This is basically the same as copy + delete - if _selected_rect.has_no_area(): - return - - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - _clipboard = image.get_rect(_selected_rect) - if _clipboard.is_invisible(): - return - - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - var brush = _clipboard.get_rect(_clipboard.get_used_rect()) - project.brushes.append(brush) - Brushes.add_project_brush(brush) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - image.blit_rect(_clear_image, rect, _selected_rect.position) - commit_undo("Draw", undo_data) - -func paste() -> void: - if _clipboard.get_size() <= Vector2.ZERO: - return - - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - image.blend_rect(_clipboard, rect, _selected_rect.position) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - commit_undo("Draw", undo_data) - - -func delete() -> void: - var undo_data = _get_undo_data(true) - var project := Global.current_project - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - var size := _selected_rect.size - var rect = Rect2(Vector2.ZERO, size) - _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) - image.blit_rect(_clear_image, rect, _selected_rect.position) - move_end() # The selection_rectangle can be used while is moving, this prevents malfunctioning - commit_undo("Draw", undo_data) - - -func commit_undo(action : String, undo_data : Dictionary) -> void: - var redo_data = _get_undo_data("image_data" in undo_data) - var project := Global.current_project - - project.undos += 1 - project.undo_redo.create_action(action) - project.undo_redo.add_do_property(project, "selected_rect", redo_data["selected_rect"]) - project.undo_redo.add_undo_property(project, "selected_rect", undo_data["selected_rect"]) - if "image_data" in undo_data: - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) - project.undo_redo.add_undo_property(image, "data", undo_data["image_data"]) - project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) - project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) - project.undo_redo.commit_action() - - -func _get_undo_data(undo_image : bool) -> Dictionary: - var data = {} - var project := Global.current_project - data["selected_rect"] = Global.current_project.selected_rect - if undo_image: - var image : Image = project.frames[project.current_frame].cels[project.current_layer].image - image.unlock() - data["image_data"] = image.data - image.lock() - return data diff --git a/src/Shaders/MarchingAntsOutline.shader b/src/Shaders/MarchingAntsOutline.shader new file mode 100644 index 000000000..24970fbd9 --- /dev/null +++ b/src/Shaders/MarchingAntsOutline.shader @@ -0,0 +1,52 @@ +// Taken and modified from https://godotshaders.com/shader/2d-outline-inline/ +// Also thanks to https://andreashackel.de/tech-art/stripes-shader-1/ for the stripe tutorial +shader_type canvas_item; + +uniform vec4 first_color : hint_color = vec4(1.0); +uniform vec4 second_color : hint_color = vec4(0.0, 0.0, 0.0, 1.0); +uniform bool animated = true; +uniform float width : hint_range(0, 2) = 0.2; +uniform float frequency = 50.0; +uniform float stripe_direction : hint_range(0, 1) = 0.5; + + +bool hasContraryNeighbour(vec2 uv, vec2 texture_pixel_size, sampler2D texture) { + for (float i = -ceil(width); i <= ceil(width); i++) { + float x = abs(i) > width ? width * sign(i) : i; + float offset = width; + + for (float j = -ceil(offset); j <= ceil(offset); j++) { + float y = abs(j) > offset ? offset * sign(j) : j; + vec2 xy = uv + texture_pixel_size * vec2(x, y); + + if ((xy != clamp(xy, vec2(0.0), vec2(1.0)) || texture(texture, xy).a == 0.0) == true) { + return true; + } + } + } + + return false; +} + +void fragment() { + vec2 uv = UV; + COLOR = texture(TEXTURE, uv); + + if ((COLOR.a > 0.0) == true && hasContraryNeighbour(uv, TEXTURE_PIXEL_SIZE, TEXTURE)) { + vec4 final_color = first_color; + // Generate diagonal stripes + if(animated) + uv -= TIME / frequency; + float pos = mix(uv.x, uv.y, stripe_direction) * frequency; + float value = floor(fract(pos) + 0.5); + if (mod(value, 2.0) == 0.0) + final_color = second_color; + + COLOR.rgb = mix(COLOR.rgb, final_color.rgb, final_color.a); + COLOR.a += (1.0 - COLOR.a) * final_color.a; + } + else { + // Erase the texture's pixels in order to only keep the outline visible + COLOR.a = 0.0; + } +} \ No newline at end of file diff --git a/src/Tools/BaseTool.gd b/src/Tools/BaseTool.gd index 66fd6dcc5..ae832e098 100644 --- a/src/Tools/BaseTool.gd +++ b/src/Tools/BaseTool.gd @@ -81,11 +81,10 @@ func draw_preview() -> void: func _get_draw_rect() -> Rect2: - if Global.current_project.selected_pixels.empty(): - return Global.current_project.tile_mode_rects[Global.TileMode.NONE] + if Global.current_project.has_selection: + return Global.current_project.get_selection_rectangle() else: - var selected_pixels = Global.current_project.selected_pixels - return Rect2(selected_pixels[0].x, selected_pixels[0].y, selected_pixels[-1].x - selected_pixels[0].x + 1, selected_pixels[-1].y - selected_pixels[0].y + 1) + return Global.current_project.tile_mode_rects[Global.TileMode.NONE] func _get_draw_image() -> Image: diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd index 3d37d7c71..af61e54cd 100644 --- a/src/Tools/Bucket.gd +++ b/src/Tools/Bucket.gd @@ -94,9 +94,10 @@ func update_pattern() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() if Global.current_project.layers[Global.current_project.current_layer].locked or !Global.current_project.tile_mode_rects[Global.TileMode.NONE].has_point(position): return - if Global.current_project.selected_pixels and not position in Global.current_project.selected_pixels: + if Global.current_project.has_selection and not Global.current_project.can_pixel_get_drawn(position): return var undo_data = _get_undo_data() if _fill_area == 0: @@ -123,17 +124,14 @@ func fill_in_color(position : Vector2) -> void: return image.lock() - var pixels := [] - if project.selected_pixels: - pixels = project.selected_pixels.duplicate() - else: - for x in Global.current_project.size.x: - for y in Global.current_project.size.y: - pixels.append(Vector2(x, y)) - for i in pixels: - if image.get_pixelv(i).is_equal_approx(color): - _set_pixel(image, i.x, i.y, tool_slot.color) + for x in Global.current_project.size.x: + for y in Global.current_project.size.y: + var pos := Vector2(x, y) + if project.has_selection and not project.can_pixel_get_drawn(pos): + continue + if image.get_pixelv(pos).is_equal_approx(color): + _set_pixel(image, x, y, tool_slot.color) func fill_in_area(position : Vector2) -> void: @@ -145,19 +143,9 @@ func fill_in_area(position : Vector2) -> void: var mirror_y = project.y_symmetry_point - position.y var mirror_x_inside : bool var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + mirror_x_inside = project.can_pixel_get_drawn(Vector2(mirror_x, position.y)) + mirror_y_inside = project.can_pixel_get_drawn(Vector2(position.x, mirror_y)) if tool_slot.horizontal_mirror and mirror_x_inside: _flood_fill(Vector2(mirror_x, position.y)) @@ -202,13 +190,8 @@ func _flood_fill(position : Vector2) -> void: func _set_pixel(image : Image, x : int, y : int, color : Color) -> void: var project : Project = Global.current_project - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - if not _get_draw_rect().has_point(Vector2(x, y)): - return - else: - if not Vector2(x, y) in project.selected_pixels: - return + if !project.can_pixel_get_drawn(Vector2(x, y)): + return if _fill_with == 0 or _pattern == null: image.set_pixel(x, y, color) diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd index 776fc9b08..4a8919a38 100644 --- a/src/Tools/Draw.gd +++ b/src/Tools/Draw.gd @@ -100,7 +100,6 @@ func update_brush() -> void: else: var random = randi() % _brush.random.size() _brush_image = _create_blended_brush_image(_brush.random[random]) - _brush_image.lock() _brush_texture.create_from_image(_brush_image, 0) update_mirror_brush() _indicator = _create_brush_indicator() @@ -114,7 +113,6 @@ func update_random_image() -> void: return var random = randi() % _brush.random.size() _brush_image = _create_blended_brush_image(_brush.random[random]) - _brush_image.lock() _brush_texture.create_from_image(_brush_image, 0) _indicator = _create_brush_indicator() update_mirror_brush() @@ -273,34 +271,43 @@ func draw_tool_brush(position : Vector2) -> void: return var src_rect := Rect2(dst_rect.position - dst, dst_rect.size) dst = dst_rect.position - - _draw_brush_image(_brush_image, src_rect, dst) + var brush_image : Image = remove_unselected_parts_of_brush(_brush_image, dst) + _draw_brush_image(brush_image, src_rect, dst) # Handle Mirroring var mirror_x = (project.x_symmetry_point + 1) - dst.x - src_rect.size.x var mirror_y = (project.y_symmetry_point + 1) - dst.y - src_rect.size.y - var mirror_x_inside : bool - var mirror_y_inside : bool - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - mirror_x_inside = mirror_x >= 0 and mirror_x < project.size.x - mirror_y_inside = mirror_y >= 0 and mirror_y < project.size.y - else: - var selected_pixels_x := [] - var selected_pixels_y := [] - for i in project.selected_pixels: - selected_pixels_x.append(i.x) - selected_pixels_y.append(i.y) - mirror_x_inside = mirror_x in selected_pixels_x - mirror_y_inside = mirror_y in selected_pixels_y + if tool_slot.horizontal_mirror: + var x_dst := Vector2(mirror_x, dst.y) + var mirror_brush_x : Image = remove_unselected_parts_of_brush(_mirror_brushes.x, x_dst) + _draw_brush_image(mirror_brush_x, _flip_rect(src_rect, size, true, false), x_dst) + if tool_slot.vertical_mirror: + var xy_dst := Vector2(mirror_x, mirror_y) + var mirror_brush_xy : Image = remove_unselected_parts_of_brush(_mirror_brushes.xy, xy_dst) + _draw_brush_image(mirror_brush_xy, _flip_rect(src_rect, size, true, true), xy_dst) + if tool_slot.vertical_mirror: + var y_dst := Vector2(dst.x, mirror_y) + var mirror_brush_y : Image = remove_unselected_parts_of_brush(_mirror_brushes.y, y_dst) + _draw_brush_image(mirror_brush_y, _flip_rect(src_rect, size, false, true), y_dst) - if tool_slot.horizontal_mirror and mirror_x_inside: - _draw_brush_image(_mirror_brushes.x, _flip_rect(src_rect, size, true, false), Vector2(mirror_x, dst.y)) - if tool_slot.vertical_mirror and mirror_y_inside: - _draw_brush_image(_mirror_brushes.xy, _flip_rect(src_rect, size, true, true), Vector2(mirror_x, mirror_y)) - if tool_slot.vertical_mirror and mirror_y_inside: - _draw_brush_image(_mirror_brushes.y, _flip_rect(src_rect, size, false, true), Vector2(dst.x, mirror_y)) + +func remove_unselected_parts_of_brush(brush : Image, dst : Vector2) -> Image: + var project : Project = Global.current_project + var size := brush.get_size() + var new_brush := Image.new() + new_brush.copy_from(_mirror_brushes.x) + if !project.has_selection: + return new_brush + + new_brush.lock() + for x in size.x: + for y in size.y: + var pos := Vector2(x, y) + dst + if !project.selection_bitmap.get_bit(pos): + new_brush.set_pixel(x, y, Color(0)) + new_brush.unlock() + return new_brush func draw_indicator() -> void: @@ -337,13 +344,8 @@ func _set_pixel(position : Vector2) -> void: if project.tile_mode and project.get_tile_mode_rect().has_point(position): position = position.posmodv(project.size) - var entire_image_selected : bool = project.selected_pixels.empty() - if entire_image_selected: - if not _get_draw_rect().has_point(position): - return - else: - if not position in project.selected_pixels: - return + if !project.can_pixel_get_drawn(position): + return var image := _get_draw_image() var i := int(position.x + position.y * image.get_size().x) diff --git a/src/Tools/Eraser.gd b/src/Tools/Eraser.gd index b73ff86a9..f24d2adb2 100644 --- a/src/Tools/Eraser.gd +++ b/src/Tools/Eraser.gd @@ -24,6 +24,7 @@ func _init() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask() _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/LightenDarken.gd b/src/Tools/LightenDarken.gd index a5452eb6c..acccf4155 100644 --- a/src/Tools/LightenDarken.gd +++ b/src/Tools/LightenDarken.gd @@ -200,6 +200,7 @@ func update_strength() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask(false) _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/Move.gd b/src/Tools/Move.gd new file mode 100644 index 000000000..e1cfd464a --- /dev/null +++ b/src/Tools/Move.gd @@ -0,0 +1,40 @@ +extends BaseTool + + +var _starting_pos : Vector2 +var _offset : Vector2 + + +func draw_start(position : Vector2) -> void: + _starting_pos = position + _offset = position + if Global.current_project.has_selection: + Global.canvas.selection.move_content_start() + + +func draw_move(position : Vector2) -> void: + if Global.current_project.has_selection: + Global.canvas.selection.move_content(position - _offset) + _offset = position + else: + Global.canvas.move_preview_location = position - _starting_pos + _offset = position + + +func draw_end(position : Vector2) -> void: + if _starting_pos != Vector2.INF: + var pixel_diff : Vector2 = position - _starting_pos + var project : Project = Global.current_project + var image : Image = _get_draw_image() + + if !project.has_selection: + Global.canvas.move_preview_location = Vector2.ZERO + var image_copy := Image.new() + image_copy.copy_from(image) + Global.canvas.handle_undo("Draw") + image.fill(Color(0, 0, 0, 0)) + image.blit_rect(image_copy, Rect2(Vector2.ZERO, project.size), pixel_diff) + + Global.canvas.handle_redo("Draw") + + _starting_pos = Vector2.INF diff --git a/src/Tools/Move.tscn b/src/Tools/Move.tscn new file mode 100644 index 000000000..82037f1ba --- /dev/null +++ b/src/Tools/Move.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/BaseTool.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/Move.gd" type="Script" id=2] + + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="PixelPerfect" parent="." index="1"] +visible = false +margin_top = 126.0 +margin_bottom = 150.0 + +[node name="EmptySpacer" parent="." index="2"] +visible = false +margin_top = 126.0 +margin_bottom = 138.0 + +[node name="Mirror" parent="." index="3"] +visible = false +margin_top = 126.0 +margin_bottom = 143.0 diff --git a/src/Tools/Pencil.gd b/src/Tools/Pencil.gd index c139a06cc..967f45bae 100644 --- a/src/Tools/Pencil.gd +++ b/src/Tools/Pencil.gd @@ -46,6 +46,7 @@ func update_config() -> void: func draw_start(position : Vector2) -> void: + Global.canvas.selection.move_content_confirm() update_mask() _changed = false _drawer.color_op.changed = false diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd index e4f10c8a5..18c5bbda9 100644 --- a/src/Tools/RectSelect.gd +++ b/src/Tools/RectSelect.gd @@ -1,56 +1,63 @@ extends BaseTool +var _rect := Rect2(0, 0, 0, 0) var _start := Rect2(0, 0, 0, 0) var _offset := Vector2.ZERO -var _drag := false var _move := false +var undo_data : Dictionary func draw_start(position : Vector2) -> void: - if Global.selection_rectangle.has_point(position): + Global.canvas.selection.move_content_confirm() + undo_data = Global.canvas.selection._get_undo_data(false) + var selection_position : Vector2 = Global.canvas.selection.big_bounding_rectangle.position + var offsetted_pos := position + if selection_position.x < 0: + offsetted_pos.x -= selection_position.x + if selection_position.y < 0: + offsetted_pos.y -= selection_position.y + + if offsetted_pos.x >= 0 and offsetted_pos.y >= 0 and Global.current_project.selection_bitmap.get_bit(offsetted_pos) and !Tools.control and !Tools.shift: + # Move current selection _move = true _offset = position - Global.selection_rectangle.move_start(Tools.shift) - _set_cursor_text(Global.selection_rectangle.get_rect()) + Global.canvas.selection.move_borders_start() + else: - _drag = true _start = Rect2(position, Vector2.ZERO) - Global.selection_rectangle.set_rect(_start) func draw_move(position : Vector2) -> void: if _move: - Global.selection_rectangle.move_rect(position - _offset) + Global.canvas.selection.move_borders(position - _offset) _offset = position - _set_cursor_text(Global.selection_rectangle.get_rect()) + _set_cursor_text(Global.canvas.selection.big_bounding_rectangle) else: - var rect := _start.expand(position).abs() - rect = rect.grow_individual(0, 0, 1, 1) - Global.selection_rectangle.set_rect(rect) - _set_cursor_text(rect) + _rect = _start.expand(position).abs() + _rect = _rect.grow_individual(0, 0, 1, 1) + _set_cursor_text(_rect) + Global.canvas.selection.drawn_rect = _rect + Global.canvas.selection.update() func draw_end(_position : Vector2) -> void: if _move: - Global.selection_rectangle.move_end() + Global.canvas.selection.move_borders_end() else: - Global.selection_rectangle.select_rect() - _drag = false + if !Tools.shift and !Tools.control: + Global.canvas.selection.clear_selection() + if _rect.size == Vector2.ZERO and Global.current_project.has_selection: + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + if _rect.size != Vector2.ZERO: + Global.canvas.selection.select_rect(_rect, !Tools.control) + Global.canvas.selection.commit_undo("Rectangle Select", undo_data) + _move = false cursor_text = "" - - -func cursor_move(position : Vector2) -> void: - if _drag: - _cursor = Vector2.INF - elif Global.selection_rectangle.has_point(position): - _cursor = Vector2.INF - Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_MOVE - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - else: - _cursor = position - Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS + _rect = Rect2(0, 0, 0, 0) + Global.canvas.selection.drawn_rect = _rect + Global.canvas.selection.update() func _set_cursor_text(rect : Rect2) -> void: diff --git a/src/UI/Canvas/CameraMovement.gd b/src/UI/Canvas/CameraMovement.gd index 689149654..090a8dfc8 100644 --- a/src/UI/Canvas/CameraMovement.gd +++ b/src/UI/Canvas/CameraMovement.gd @@ -178,6 +178,9 @@ func zoom_changed() -> void: update_rulers() for guide in Global.current_project.guides: guide.width = zoom.x * 2 + + Global.canvas.selection.update_on_zoom(zoom.x) + elif name == "CameraPreview": Global.preview_zoom_slider.value = -zoom.x diff --git a/src/UI/Canvas/Canvas.gd b/src/UI/Canvas/Canvas.gd index 92e4d3579..74e759149 100644 --- a/src/UI/Canvas/Canvas.gd +++ b/src/UI/Canvas/Canvas.gd @@ -7,12 +7,14 @@ var current_pixel := Vector2.ZERO var can_undo := true var cursor_image_has_changed := false var sprite_changed_this_frame := false # for optimization purposes +var move_preview_location := Vector2.ZERO onready var currently_visible_frame : Viewport = $CurrentlyVisibleFrame onready var current_frame_drawer = $CurrentlyVisibleFrame/CurrentFrameDrawer +onready var tile_mode = $TileMode onready var pixel_grid = $PixelGrid onready var grid = $Grid -onready var tile_mode = $TileMode +onready var selection = $Selection onready var indicators = $Indicators onready var previews = $Previews @@ -30,7 +32,7 @@ func _draw() -> void: Global.small_preview_viewport.get_child(0).get_node("CanvasPreview").update() var current_cels : Array = Global.current_project.frames[Global.current_project.current_frame].cels - + var current_layer : int = Global.current_project.current_layer var _position := position var _scale := scale if Global.mirror_view: @@ -41,7 +43,10 @@ func _draw() -> void: for i in range(Global.current_project.layers.size()): var modulate_color := Color(1, 1, 1, current_cels[i].opacity) if Global.current_project.layers[i].visible: # if it's visible - draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) + if i == current_layer: + draw_texture(current_cels[i].image_texture, move_preview_location, modulate_color) + else: + draw_texture(current_cels[i].image_texture, Vector2.ZERO, modulate_color) if Global.onion_skinning: onion_skinning() diff --git a/src/UI/Canvas/Canvas.tscn b/src/UI/Canvas/Canvas.tscn index 75e266ffa..c23c9d8c2 100644 --- a/src/UI/Canvas/Canvas.tscn +++ b/src/UI/Canvas/Canvas.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=9 format=2] +[gd_scene load_steps=12 format=2] [ext_resource path="res://src/UI/Canvas/Canvas.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/Grid.gd" type="Script" id=2] @@ -7,10 +7,21 @@ [ext_resource path="res://src/UI/Canvas/CurrentFrameDrawer.gd" type="Script" id=5] [ext_resource path="res://src/UI/Canvas/PixelGrid.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/Previews.gd" type="Script" id=7] +[ext_resource path="res://src/UI/Canvas/Selection.gd" type="Script" id=8] +[ext_resource path="res://src/Shaders/MarchingAntsOutline.shader" type="Shader" id=9] [sub_resource type="CanvasItemMaterial" id=1] blend_mode = 4 +[sub_resource type="ShaderMaterial" id=2] +shader = ExtResource( 9 ) +shader_param/first_color = Color( 1, 1, 1, 1 ) +shader_param/second_color = Color( 0, 0, 0, 1 ) +shader_param/animated = true +shader_param/width = 0.05 +shader_param/frequency = 200.0 +shader_param/stripe_direction = 0.5 + [node name="Canvas" type="Node2D"] script = ExtResource( 1 ) @@ -36,6 +47,13 @@ script = ExtResource( 6 ) [node name="Grid" type="Node2D" parent="."] script = ExtResource( 2 ) +[node name="Selection" type="Node2D" parent="."] +script = ExtResource( 8 ) + +[node name="MarchingAntsOutline" type="Sprite" parent="Selection"] +material = SubResource( 2 ) +centered = false + [node name="Indicators" type="Node2D" parent="."] script = ExtResource( 3 ) diff --git a/src/UI/Canvas/Selection.gd b/src/UI/Canvas/Selection.gd new file mode 100644 index 000000000..469f6eeb4 --- /dev/null +++ b/src/UI/Canvas/Selection.gd @@ -0,0 +1,498 @@ +extends Node2D + + +class Clipboard: + var image := Image.new() + var selection_bitmap := BitMap.new() + var big_bounding_rectangle := Rect2() + var selection_offset := Vector2.ZERO + + +class Gizmo: + enum Type {SCALE, ROTATE} + + var rect : Rect2 + var direction := Vector2.ZERO + var type : int + + func _init(_type : int = Type.SCALE, _direction := Vector2.ZERO) -> void: + type = _type + direction = _direction + + + func get_cursor() -> int: + var cursor := Input.CURSOR_MOVE + if direction == Vector2.ZERO: + return Input.CURSOR_POINTING_HAND + elif direction == Vector2(-1, -1) or direction == Vector2(1, 1): # Top left or bottom right + cursor = Input.CURSOR_FDIAGSIZE + elif direction == Vector2(1, -1) or direction == Vector2(-1, 1): # Top right or bottom left + cursor = Input.CURSOR_BDIAGSIZE + elif direction == Vector2(0, -1) or direction == Vector2(0, 1): # Center top or center bottom + cursor = Input.CURSOR_VSIZE + elif direction == Vector2(-1, 0) or direction == Vector2(1, 0): # Center left or center right + cursor = Input.CURSOR_HSIZE + return cursor + + +var clipboard := Clipboard.new() +var is_moving_content := false +var big_bounding_rectangle := Rect2() setget _big_bounding_rectangle_changed +var temp_rect := Rect2() +var original_big_bounding_rectangle := Rect2() +var original_preview_image := Image.new() +var original_bitmap := BitMap.new() +var original_offset := Vector2.ZERO +var preview_image := Image.new() +var preview_image_texture := ImageTexture.new() +var undo_data : Dictionary +var drawn_rect := Rect2(0, 0, 0, 0) +var gizmos := [] # Array of Gizmos +var dragged_gizmo : Gizmo = null +var prev_angle := 0 +var mouse_pos_on_gizmo_drag := Vector2.ZERO + +onready var marching_ants_outline : Sprite = $MarchingAntsOutline + + +func _ready() -> void: + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, -1))) # Top left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(0, -1))) # Center top + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, -1))) # Top right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, 0))) # Center right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(1, 1))) # Bottom right + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(0, 1))) # Center bottom + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 1))) # Bottom left + gizmos.append(Gizmo.new(Gizmo.Type.SCALE, Vector2(-1, 0))) # Center left + +# gizmos.append(Gizmo.new(Gizmo.Type.ROTATE)) # Rotation gizmo (temp) + + +func _input(event : InputEvent) -> void: + if event is InputEventKey: + if is_moving_content: # Temporary code + if event.scancode == 16777221: # Enter + move_content_confirm() + elif event.scancode == 16777217: # Escape + move_content_cancel() + elif event is InputEventMouse: + var gizmo : Gizmo + for g in gizmos: + if g.rect.has_point(Global.canvas.current_pixel): + gizmo = g + break + if gizmo: + Global.main_viewport.mouse_default_cursor_shape = gizmo.get_cursor() + elif !dragged_gizmo: + Global.main_viewport.mouse_default_cursor_shape = Input.CURSOR_CROSS + + if event is InputEventMouseButton and event.button_index == BUTTON_LEFT: + if event.pressed: + if gizmo: + Global.has_focus = false + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + dragged_gizmo = gizmo + temp_rect = big_bounding_rectangle + move_content_start() + Global.current_project.selection_offset = Vector2.ZERO + if gizmo.type == Gizmo.Type.ROTATE: + var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) + original_preview_image.crop(img_size, img_size) + + elif dragged_gizmo: + Global.has_focus = true + dragged_gizmo = null + + if dragged_gizmo: + if dragged_gizmo.type == Gizmo.Type.SCALE: + gizmo_resize() + else: + gizmo_rotate() + + +func _draw() -> void: + var _position := position + var _scale := scale + if Global.mirror_view: + _position.x = _position.x + Global.current_project.size.x + _scale.x = -1 + draw_set_transform(_position, rotation, _scale) + draw_rect(drawn_rect, Color.black, false) + if big_bounding_rectangle.size != Vector2.ZERO: + for gizmo in gizmos: # Draw gizmos + draw_rect(gizmo.rect, Color.black) + var filled_rect : Rect2 = gizmo.rect + var filled_size : Vector2 = gizmo.rect.size * Vector2(0.2, 0.2) + filled_rect.position += filled_size + filled_rect.size -= filled_size * 2 + draw_rect(filled_rect, Color.white) # Filled white square + + if is_moving_content and !preview_image.is_empty(): + draw_texture(preview_image_texture, big_bounding_rectangle.position, Color(1, 1, 1, 0.5)) + draw_set_transform(position, rotation, scale) + + +func _big_bounding_rectangle_changed(value : Rect2) -> void: + big_bounding_rectangle = value + update_gizmos() + + +func update_gizmos() -> void: + var rect_pos : Vector2 = big_bounding_rectangle.position + var rect_end : Vector2 = big_bounding_rectangle.end + var size := Vector2.ONE * Global.camera.zoom * 10 + # Clockwise, starting from top-left corner + gizmos[0].rect = Rect2(rect_pos - size, size) + gizmos[1].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y), size) + gizmos[2].rect = Rect2(Vector2(rect_end.x, rect_pos.y - size.y), size) + gizmos[3].rect = Rect2(Vector2(rect_end.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + gizmos[4].rect = Rect2(rect_end, size) + gizmos[5].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_end.y), size) + gizmos[6].rect = Rect2(Vector2(rect_pos.x - size.x, rect_end.y), size) + gizmos[7].rect = Rect2(Vector2(rect_pos.x - size.x, (rect_end.y + rect_pos.y - size.y) / 2), size) + + # Rotation gizmo (temp) +# gizmos[8].rect = Rect2(Vector2((rect_end.x + rect_pos.x - size.x) / 2, rect_pos.y - size.y - (size.y * 2)), size) + update() + + +func update_on_zoom(zoom : float) -> void: + var size := max(Global.current_project.selection_bitmap.get_size().x, Global.current_project.selection_bitmap.get_size().y) + marching_ants_outline.material.set_shader_param("width", zoom) + marching_ants_outline.material.set_shader_param("frequency", (1.0 / zoom) * 10 * size / 64) + for gizmo in gizmos: + if gizmo.rect.size == Vector2.ZERO: + return + update_gizmos() + + +func gizmo_resize() -> void: + var diff : Vector2 = (Global.canvas.current_pixel - mouse_pos_on_gizmo_drag) * dragged_gizmo.direction + var dir := dragged_gizmo.direction + if diff != Vector2.ZERO: + mouse_pos_on_gizmo_drag = Global.canvas.current_pixel + var left := 0.0 if dir.x >= 0 else diff.x + var top := 0.0 if dir.y >= 0 else diff.y + var right := diff.x if dir.x >= 0 else 0.0 + var bottom := diff.y if dir.y >= 0 else 0.0 + temp_rect = temp_rect.grow_individual(left, top, right, bottom) + big_bounding_rectangle = temp_rect.abs() + big_bounding_rectangle.position = big_bounding_rectangle.position.ceil() + self.big_bounding_rectangle.size = big_bounding_rectangle.size.ceil() + var size = big_bounding_rectangle.size.abs() + preview_image.copy_from(original_preview_image) + preview_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + if temp_rect.size.x < 0: + preview_image.flip_x() + if temp_rect.size.y < 0: + preview_image.flip_y() + preview_image_texture.create_from_image(preview_image, 0) + Global.current_project.selection_bitmap = Global.current_project.resize_bitmap_values(original_bitmap, size, temp_rect.size.x < 0, temp_rect.size.y < 0) + Global.current_project.selection_bitmap_changed() + update() + + +func gizmo_rotate() -> void: # Does not work properly yet + var angle := Global.canvas.current_pixel.angle_to_point(mouse_pos_on_gizmo_drag) + angle = deg2rad(floor(rad2deg(angle))) + if angle == prev_angle: + return + prev_angle = angle +# var img_size := max(original_preview_image.get_width(), original_preview_image.get_height()) +# warning-ignore:integer_division +# warning-ignore:integer_division +# var pivot = Vector2(original_preview_image.get_width() / 2, original_preview_image.get_height() / 2) + var pivot = Vector2(big_bounding_rectangle.size.x / 2, big_bounding_rectangle.size.y / 2) + preview_image.copy_from(original_preview_image) + if original_big_bounding_rectangle.position != big_bounding_rectangle.position: + preview_image.fill(Color(0, 0, 0, 0)) + var pos_diff := (original_big_bounding_rectangle.position - big_bounding_rectangle.position).abs() +# pos_diff.y = 0 + preview_image.blit_rect(original_preview_image, Rect2(Vector2.ZERO, preview_image.get_size()), pos_diff) + DrawingAlgos.nn_rotate(preview_image, angle, pivot) + preview_image_texture.create_from_image(preview_image, 0) + + var bitmap_image = Global.current_project.bitmap_to_image(original_bitmap) + var bitmap_pivot = original_big_bounding_rectangle.position + ((original_big_bounding_rectangle.end - original_big_bounding_rectangle.position) / 2) + DrawingAlgos.nn_rotate(bitmap_image, angle, bitmap_pivot) + Global.current_project.selection_bitmap.create_from_image_alpha(bitmap_image) + Global.current_project.selection_bitmap_changed() + self.big_bounding_rectangle = bitmap_image.get_used_rect() + update() + + +func select_rect(rect : Rect2, select := true) -> void: + var project : Project = Global.current_project + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + var offset_position := Vector2.ZERO # Used only if the selection is outside of the canvas boundaries, on the left and/or above (negative coords) + if big_bounding_rectangle.position.x < 0: + rect.position.x -= big_bounding_rectangle.position.x + offset_position.x = big_bounding_rectangle.position.x + if big_bounding_rectangle.position.y < 0: + rect.position.y -= big_bounding_rectangle.position.y + offset_position.y = big_bounding_rectangle.position.y + + if offset_position != Vector2.ZERO: + big_bounding_rectangle.position -= offset_position + project.move_bitmap_values(selection_bitmap_copy) + + selection_bitmap_copy.set_bit_rect(rect, select) + big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + + if offset_position != Vector2.ZERO: + big_bounding_rectangle.position += offset_position + project.move_bitmap_values(selection_bitmap_copy) + + project.selection_bitmap = selection_bitmap_copy + self.big_bounding_rectangle = big_bounding_rectangle # call getter method + + + +func move_borders_start() -> void: + undo_data = _get_undo_data(false) + + +func move_borders(move : Vector2) -> void: + marching_ants_outline.offset += move + self.big_bounding_rectangle.position += move + update() + + +func move_borders_end() -> void: + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy) + + Global.current_project.selection_bitmap = selected_bitmap_copy + commit_undo("Rectangle Select", undo_data) + update() + + +func move_content_start() -> void: + if !is_moving_content: + is_moving_content = true + undo_data = _get_undo_data(true) + get_preview_image() + original_bitmap = Global.current_project.selection_bitmap.duplicate() + original_big_bounding_rectangle = big_bounding_rectangle + original_offset = Global.current_project.selection_offset + update() + + +func move_content(move : Vector2) -> void: + move_borders(move) + + +func move_content_confirm() -> void: + if !is_moving_content: + return + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) + var selected_bitmap_copy = Global.current_project.selection_bitmap.duplicate() + Global.current_project.move_bitmap_values(selected_bitmap_copy) + Global.current_project.selection_bitmap = selected_bitmap_copy + + original_preview_image = Image.new() + preview_image = Image.new() + original_bitmap = BitMap.new() + is_moving_content = false + commit_undo("Move Selection", undo_data) + update() + + +func move_content_cancel() -> void: + if preview_image.is_empty(): + return + var project : Project = Global.current_project + project.selection_offset = original_offset + + is_moving_content = false + self.big_bounding_rectangle = original_big_bounding_rectangle + project.selection_bitmap = original_bitmap + project.selection_bitmap_changed() + preview_image = original_preview_image + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + cel_image.blit_rect_mask(preview_image, preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) + Global.canvas.update_texture(project.current_layer) + original_preview_image = Image.new() + preview_image = Image.new() + original_bitmap = BitMap.new() + update() + + +func commit_undo(action : String, _undo_data : Dictionary) -> void: + var redo_data = _get_undo_data("image_data" in _undo_data) + var project := Global.current_project + + project.undos += 1 + project.undo_redo.create_action(action) + project.undo_redo.add_do_property(project, "selection_bitmap", redo_data["selection_bitmap"]) + project.undo_redo.add_do_property(self, "big_bounding_rectangle", redo_data["big_bounding_rectangle"]) + project.undo_redo.add_do_property(project, "selection_offset", redo_data["outline_offset"]) + + project.undo_redo.add_undo_property(project, "selection_bitmap", _undo_data["selection_bitmap"]) + project.undo_redo.add_undo_property(self, "big_bounding_rectangle", _undo_data["big_bounding_rectangle"]) + project.undo_redo.add_undo_property(project, "selection_offset", _undo_data["outline_offset"]) + + + if "image_data" in _undo_data: + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + project.undo_redo.add_do_property(image, "data", redo_data["image_data"]) + project.undo_redo.add_undo_property(image, "data", _undo_data["image_data"]) + project.undo_redo.add_do_method(Global, "redo", project.current_frame, project.current_layer) + project.undo_redo.add_do_method(project, "selection_bitmap_changed") + project.undo_redo.add_undo_method(Global, "undo", project.current_frame, project.current_layer) + project.undo_redo.add_undo_method(project, "selection_bitmap_changed") + project.undo_redo.commit_action() + + undo_data.clear() + + +func _get_undo_data(undo_image : bool) -> Dictionary: + var data := {} + var project := Global.current_project + data["selection_bitmap"] = project.selection_bitmap + data["big_bounding_rectangle"] = big_bounding_rectangle + data["outline_offset"] = Global.current_project.selection_offset + + if undo_image: + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + image.unlock() + data["image_data"] = image.data + image.lock() + + return data + + +func cut() -> void: + copy() + delete() + + +func copy() -> void: + var project := Global.current_project + if !project.has_selection: + return + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + var to_copy := Image.new() + to_copy = image.get_rect(big_bounding_rectangle) + to_copy.lock() + # Only remove unincluded pixels if the selection is not a single rectangle + for x in to_copy.get_size().x: + for y in to_copy.get_size().y: + var pos := Vector2(x, y) + var offset_pos = big_bounding_rectangle.position + if offset_pos.x < 0: + offset_pos.x = 0 + if offset_pos.y < 0: + offset_pos.y = 0 + if not project.selection_bitmap.get_bit(pos + offset_pos): + to_copy.set_pixelv(pos, Color(0)) + to_copy.unlock() + clipboard.image = to_copy + clipboard.selection_bitmap = project.selection_bitmap.duplicate() + clipboard.big_bounding_rectangle = big_bounding_rectangle + clipboard.selection_offset = project.selection_offset + + +func paste() -> void: + if !clipboard.image: + return + var _undo_data = _get_undo_data(true) + var project := Global.current_project + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + clear_selection() + project.selection_bitmap = clipboard.selection_bitmap.duplicate() + self.big_bounding_rectangle = clipboard.big_bounding_rectangle + project.selection_offset = clipboard.selection_offset + image.blend_rect(clipboard.image, Rect2(Vector2.ZERO, project.size), big_bounding_rectangle.position) + commit_undo("Draw", _undo_data) + + +func delete() -> void: + var project := Global.current_project + if !project.has_selection: + return + var _undo_data = _get_undo_data(true) + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + for x in big_bounding_rectangle.size.x: + for y in big_bounding_rectangle.size.y: + var pos := Vector2(x, y) + big_bounding_rectangle.position + if project.can_pixel_get_drawn(pos): + image.set_pixelv(pos, Color(0)) + commit_undo("Draw", _undo_data) + + +func select_all() -> void: + var project := Global.current_project + var _undo_data = _get_undo_data(false) + clear_selection() + var full_rect = Rect2(Vector2.ZERO, project.size) + select_rect(full_rect) + commit_undo("Rectangle Select", _undo_data) + + +func invert() -> void: + move_content_confirm() + var project := Global.current_project + var _undo_data = _get_undo_data(false) + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + selection_bitmap_copy = project.resize_bitmap(selection_bitmap_copy, project.size) + project.invert_bitmap(selection_bitmap_copy) + project.selection_bitmap = selection_bitmap_copy + Global.current_project.selection_bitmap_changed() + self.big_bounding_rectangle = project.get_selection_rectangle(selection_bitmap_copy) + project.selection_offset = Vector2.ZERO + commit_undo("Rectangle Select", _undo_data) + + +func clear_selection(use_undo := false) -> void: + var project := Global.current_project + if !project.has_selection: + return + move_content_confirm() + var _undo_data = _get_undo_data(false) + var selection_bitmap_copy : BitMap = project.selection_bitmap.duplicate() + selection_bitmap_copy = project.resize_bitmap(selection_bitmap_copy, project.size) + var full_rect = Rect2(Vector2.ZERO, selection_bitmap_copy.get_size()) + selection_bitmap_copy.set_bit_rect(full_rect, false) + project.selection_bitmap = selection_bitmap_copy + + self.big_bounding_rectangle = Rect2() + project.selection_offset = Vector2.ZERO + update() + if use_undo: + commit_undo("Clear Selection", _undo_data) + + +func get_preview_image() -> void: + var project : Project = Global.current_project + var cel_image : Image = project.frames[project.current_frame].cels[project.current_layer].image + if original_preview_image.is_empty(): +# original_preview_image.copy_from(cel_image) + original_preview_image = cel_image.get_rect(big_bounding_rectangle) + original_preview_image.lock() + # For non-rectangular selections + for x in range(0, big_bounding_rectangle.size.x): + for y in range(0, big_bounding_rectangle.size.y): + var pos := Vector2(x, y) + if !project.can_pixel_get_drawn(pos + big_bounding_rectangle.position): + original_preview_image.set_pixelv(pos, Color(0, 0, 0, 0)) + + original_preview_image.unlock() + preview_image.copy_from(original_preview_image) + preview_image_texture.create_from_image(preview_image, 0) + + var clear_image := Image.new() + clear_image.create(original_preview_image.get_width(), original_preview_image.get_height(), false, Image.FORMAT_RGBA8) + cel_image.blit_rect_mask(clear_image, original_preview_image, Rect2(Vector2.ZERO, Global.current_project.selection_bitmap.get_size()), big_bounding_rectangle.position) + Global.canvas.update_texture(project.current_layer) + + +func get_big_bounding_rectangle() -> Rect2: + # Returns a rectangle that contains the entire selection, with multiple polygons + var project : Project = Global.current_project + var rect := Rect2() + var image : Image = project.bitmap_to_image(project.selection_bitmap) + rect = image.get_used_rect() + return rect diff --git a/src/UI/Dialogs/ExportDialog.gd b/src/UI/Dialogs/ExportDialog.gd index c54b21158..0186a2bce 100644 --- a/src/UI/Dialogs/ExportDialog.gd +++ b/src/UI/Dialogs/ExportDialog.gd @@ -222,6 +222,7 @@ func set_export_progress_bar(value: float) -> void: func _on_ExportDialog_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() # If we're on HTML5, don't let the user change the directory path if OS.get_name() == "HTML5": path_container.visible = false diff --git a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd index 3fc702001..ad73205f6 100644 --- a/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd +++ b/src/UI/Dialogs/ImageEffects/DesaturateDialog.gd @@ -13,8 +13,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.desaturate_image(_cel, _pixels, red, green, blue, alpha) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.desaturate_image(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd index a49260acd..d55df164a 100644 --- a/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd +++ b/src/UI/Dialogs/ImageEffects/FlipImageDialog.gd @@ -11,8 +11,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, project : Project = Global.current_project) -> void: - flip_image(_cel, _pixels, project) +func commit_action(_cel : Image, project : Project = Global.current_project) -> void: + flip_image(_cel, selection_checkbox.pressed, project) func _on_FlipHorizontal_toggled(_button_pressed : bool) -> void: @@ -23,14 +23,8 @@ func _on_FlipVertical_toggled(_button_pressed : bool) -> void: update_preview() -func _on_SelectionCheckBox_toggled(button_pressed : bool) -> void: - ._on_SelectionCheckBox_toggled(button_pressed) - update_preview() - - -func flip_image(image : Image, _pixels : Array, project : Project = Global.current_project) -> void: - var entire_image_selected : bool = _pixels.size() == project.size.x * project.size.y - if entire_image_selected: +func flip_image(image : Image, affect_selection : bool, project : Project = Global.current_project) -> void: + if !(affect_selection and project.has_selection): if flip_h.pressed: image.flip_x() if flip_v.pressed: @@ -41,10 +35,13 @@ func flip_image(image : Image, _pixels : Array, project : Project = Global.curre selected_image.create(image.get_width(), image.get_height(), false, Image.FORMAT_RGBA8) selected_image.lock() image.lock() - for i in _pixels: - var color : Color = image.get_pixelv(i) - selected_image.set_pixelv(i, color) - image.set_pixelv(i, Color(0, 0, 0, 0)) + for x in image.get_width(): + for y in image.get_width(): + var pos := Vector2(x, y) + if project.can_pixel_get_drawn(pos): + var color : Color = image.get_pixelv(pos) + selected_image.set_pixelv(pos, color) + image.set_pixelv(pos, Color(0, 0, 0, 0)) if flip_h.pressed: selected_image.flip_x() diff --git a/src/UI/Dialogs/ImageEffects/GradientDialog.gd b/src/UI/Dialogs/ImageEffects/GradientDialog.gd index 0e0c87a40..83091075d 100644 --- a/src/UI/Dialogs/ImageEffects/GradientDialog.gd +++ b/src/UI/Dialogs/ImageEffects/GradientDialog.gd @@ -18,8 +18,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, _pixels) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.generate_gradient(_cel, [color1.color, color2.color], steps.value, direction.selected, selection_checkbox.pressed, _project) func _on_ColorPickerButton_color_changed(_color : Color) -> void: diff --git a/src/UI/Dialogs/ImageEffects/HSVDialog.gd b/src/UI/Dialogs/ImageEffects/HSVDialog.gd index c77a0d694..2ab3b4a03 100644 --- a/src/UI/Dialogs/ImageEffects/HSVDialog.gd +++ b/src/UI/Dialogs/ImageEffects/HSVDialog.gd @@ -21,8 +21,8 @@ func _confirmed() -> void: reset() -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, _pixels) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.adjust_hsv(_cel, hue_slider.value, sat_slider.value, val_slider.value, selection_checkbox.pressed, _project) func reset() -> void: diff --git a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd index 08d05c662..a1cd387e3 100644 --- a/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd +++ b/src/UI/Dialogs/ImageEffects/InvertColorsDialog.gd @@ -13,8 +13,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.invert_image_colors(_cel, _pixels, red, green, blue, alpha) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.invert_image_colors(_cel, selection_checkbox.pressed, _project, red, green, blue, alpha) func _on_RButton_toggled(button_pressed : bool) -> void: diff --git a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd index f6c1f5297..8df287320 100644 --- a/src/UI/Dialogs/ImageEffects/OutlineDialog.gd +++ b/src/UI/Dialogs/ImageEffects/OutlineDialog.gd @@ -20,8 +20,8 @@ func set_nodes() -> void: affect_option_button = $VBoxContainer/OptionsContainer/AffectOptionButton -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: - DrawingAlgos.generate_outline(_cel, _pixels, color, thickness, diagonal, inside_image) +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: + DrawingAlgos.generate_outline(_cel, selection_checkbox.pressed, _project, color, thickness, diagonal, inside_image) func _on_ThickValue_value_changed(value : int) -> void: diff --git a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd index ee26c695a..198cabf09 100644 --- a/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd +++ b/src/UI/Dialogs/ImageEffects/ResizeCanvas.gd @@ -16,6 +16,7 @@ onready var preview_rect : TextureRect = $VBoxContainer/Preview func _on_ResizeCanvas_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() image = Image.new() image.create(Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8) image.lock() diff --git a/src/UI/Dialogs/ImageEffects/RotateImage.gd b/src/UI/Dialogs/ImageEffects/RotateImage.gd index 8aa5dcd95..465ef6322 100644 --- a/src/UI/Dialogs/ImageEffects/RotateImage.gd +++ b/src/UI/Dialogs/ImageEffects/RotateImage.gd @@ -23,15 +23,38 @@ func _about_to_show() -> void: angle_hslider.value = 0 -func commit_action(_cel : Image, _pixels : Array, _project : Project = Global.current_project) -> void: +func commit_action(_cel : Image, _project : Project = Global.current_project) -> void: var angle : float = deg2rad(angle_hslider.value) +# warning-ignore:integer_division +# warning-ignore:integer_division + var pivot = Vector2(_cel.get_width() / 2, _cel.get_height() / 2) + var image := Image.new() + image.copy_from(_cel) + if _project.has_selection and selection_checkbox.pressed: + var selection_rectangle : Rect2 = _project.get_selection_rectangle() + pivot = selection_rectangle.position + ((selection_rectangle.end - selection_rectangle.position) / 2) + image.lock() + _cel.lock() + for x in _project.size.x: + for y in _project.size.y: + var pos := Vector2(x, y) + if !_project.can_pixel_get_drawn(pos): + image.set_pixelv(pos, Color(0, 0, 0, 0)) + else: + _cel.set_pixelv(pos, Color(0, 0, 0, 0)) + image.unlock() + _cel.unlock() match type_option_button.text: "Rotxel": - DrawingAlgos.rotxel(_cel, angle, _pixels) + DrawingAlgos.rotxel(image, angle, pivot) "Nearest neighbour": - DrawingAlgos.nn_rotate(_cel, angle, _pixels) + DrawingAlgos.nn_rotate(image, angle, pivot) "Upscale, Rotate and Downscale": - DrawingAlgos.fake_rotsprite(_cel, angle, _pixels) + DrawingAlgos.fake_rotsprite(image, angle, pivot) + if _project.has_selection and selection_checkbox.pressed: + _cel.blend_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) + else: + _cel.blit_rect(image, Rect2(Vector2.ZERO, image.get_size()), Vector2.ZERO) func _confirmed() -> void: diff --git a/src/UI/Dialogs/ImageEffects/ScaleImage.gd b/src/UI/Dialogs/ImageEffects/ScaleImage.gd index 89b5df288..1d4ac430b 100644 --- a/src/UI/Dialogs/ImageEffects/ScaleImage.gd +++ b/src/UI/Dialogs/ImageEffects/ScaleImage.gd @@ -12,6 +12,7 @@ onready var ratio_box : BaseButton = find_node("AspectRatioButton") func _on_ScaleImage_about_to_show() -> void: + Global.canvas.selection.move_content_confirm() width_value.value = Global.current_project.size.x height_value.value = Global.current_project.size.y width_value_perc.value = 100 diff --git a/src/UI/ToolButtons.gd b/src/UI/ToolButtons.gd index fc7643873..178514296 100644 --- a/src/UI/ToolButtons.gd +++ b/src/UI/ToolButtons.gd @@ -4,6 +4,7 @@ extends VBoxContainer # Node, shortcut onready var tools := [ [$RectSelect, "rectangle_select"], + [$Move, "move"], [$Zoom, "zoom"], [$Pan, "pan"], [$ColorPicker, "colorpicker"], diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 9607478f2..69b76353d 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -2,21 +2,37 @@ extends Panel enum FileMenuId {NEW, OPEN, OPEN_LAST_PROJECT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, QUIT} -enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, CLEAR_SELECTION, PREFERENCES} +enum EditMenuId {UNDO, REDO, COPY, CUT, PASTE, DELETE, PREFERENCES} enum ViewMenuId {TILE_MODE, WINDOW_TRANSPARENCY, PANEL_LAYOUT, MIRROR_VIEW, SHOW_GRID, SHOW_PIXEL_GRID, SHOW_RULERS, SHOW_GUIDES, SHOW_ANIMATION_TIMELINE, ZEN_MODE, FULLSCREEN_MODE} -enum ImageMenuId {SCALE_IMAGE,CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} +enum ImageMenuId {SCALE_IMAGE, CENTRALIZE_IMAGE, CROP_IMAGE, RESIZE_CANVAS, FLIP, ROTATE, INVERT_COLORS, DESATURATION, OUTLINE, HSV, GRADIENT, SHADER} +enum SelectMenuId {SELECT_ALL, CLEAR_SELECTION, INVERT} enum HelpMenuId {VIEW_SPLASH_SCREEN, ONLINE_DOCS, ISSUE_TRACKER, CHANGELOG, ABOUT_PIXELORAMA} +var file_menu_button : MenuButton +var edit_menu_button : MenuButton +var view_menu_button : MenuButton +var image_menu_button : MenuButton +var select_menu_button : MenuButton +var help_menu_button : MenuButton + var file_menu : PopupMenu var view_menu : PopupMenu var zen_mode := false func _ready() -> void: + file_menu_button = find_node("FileMenu") + edit_menu_button = find_node("EditMenu") + view_menu_button = find_node("ViewMenu") + image_menu_button = find_node("ImageMenu") + select_menu_button = find_node("SelectMenu") + help_menu_button = find_node("HelpMenu") + setup_file_menu() setup_edit_menu() setup_view_menu() setup_image_menu() + setup_select_menu() setup_help_menu() @@ -32,7 +48,7 @@ func setup_file_menu() -> void: "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(), } - file_menu = Global.file_menu.get_popup() + file_menu = file_menu_button.get_popup() var i := 0 for item in file_menu_items.keys(): @@ -65,10 +81,9 @@ func setup_edit_menu() -> void: "Cut" : InputMap.get_action_list("cut")[0].get_scancode_with_modifiers(), "Paste" : InputMap.get_action_list("paste")[0].get_scancode_with_modifiers(), "Delete" : InputMap.get_action_list("delete")[0].get_scancode_with_modifiers(), - "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), "Preferences" : 0 } - var edit_menu : PopupMenu = Global.edit_menu.get_popup() + var edit_menu : PopupMenu = edit_menu_button.get_popup() var i := 0 for item in edit_menu_items.keys(): @@ -92,7 +107,7 @@ func setup_view_menu() -> void: "Zen Mode" : InputMap.get_action_list("zen_mode")[0].get_scancode_with_modifiers(), "Fullscreen Mode" : InputMap.get_action_list("toggle_fullscreen")[0].get_scancode_with_modifiers(), } - view_menu = Global.view_menu.get_popup() + view_menu = view_menu_button.get_popup() var i := 0 for item in view_menu_items.keys(): @@ -147,7 +162,7 @@ func setup_image_menu() -> void: "Gradient" : 0, # "Shader" : 0 } - var image_menu : PopupMenu = Global.image_menu.get_popup() + var image_menu : PopupMenu = image_menu_button.get_popup() var i := 0 for item in image_menu_items.keys(): @@ -159,6 +174,22 @@ func setup_image_menu() -> void: image_menu.connect("id_pressed", self, "image_menu_id_pressed") +func setup_select_menu() -> void: + var select_menu_items := { # order as in EditMenuId enum + "Select All" : InputMap.get_action_list("select_all")[0].get_scancode_with_modifiers(), + "Clear Selection" : InputMap.get_action_list("clear_selection")[0].get_scancode_with_modifiers(), + "Invert" : InputMap.get_action_list("invert_selection")[0].get_scancode_with_modifiers(), + } + var select_menu : PopupMenu = select_menu_button.get_popup() + var i := 0 + + for item in select_menu_items.keys(): + select_menu.add_item(item, i, select_menu_items[item]) + i += 1 + + select_menu.connect("id_pressed", self, "select_menu_id_pressed") + + func setup_help_menu() -> void: var help_menu_items := { # order as in HelpMenuId enum "View Splash Screen" : 0, @@ -167,7 +198,7 @@ func setup_help_menu() -> void: "Changelog" : 0, "About Pixelorama" : 0 } - var help_menu : PopupMenu = Global.help_menu.get_popup() + var help_menu : PopupMenu = help_menu_button.get_popup() var i := 0 for item in help_menu_items.keys(): @@ -259,22 +290,17 @@ func on_recent_projects_submenu_id_pressed(id : int) -> void: func edit_menu_id_pressed(id : int) -> void: match id: EditMenuId.UNDO: - Global.current_project.undo_redo.undo() + Global.current_project.commit_undo() EditMenuId.REDO: - Global.control.redone = true - Global.current_project.undo_redo.redo() - Global.control.redone = false + Global.current_project.commit_redo() EditMenuId.COPY: - Global.selection_rectangle.copy() + Global.canvas.selection.copy() EditMenuId.CUT: - Global.selection_rectangle.cut() + Global.canvas.selection.cut() EditMenuId.PASTE: - Global.selection_rectangle.paste() + Global.canvas.selection.paste() EditMenuId.DELETE: - Global.selection_rectangle.delete() - EditMenuId.CLEAR_SELECTION: - Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0)) - Global.selection_rectangle.select_rect() + Global.canvas.selection.delete() EditMenuId.PREFERENCES: Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.dialog_open(true) @@ -343,6 +369,12 @@ func window_transparency(value :float) -> void: func toggle_mirror_view() -> void: Global.mirror_view = !Global.mirror_view + Global.canvas.selection.marching_ants_outline.scale.x = -Global.canvas.selection.marching_ants_outline.scale.x + if Global.mirror_view: + Global.canvas.selection.marching_ants_outline.position.x = Global.canvas.selection.marching_ants_outline.position.x + Global.current_project.size.x + else: + Global.canvas.selection.marching_ants_outline.position.x = 0 + Global.canvas.selection.update() view_menu.set_item_checked(ViewMenuId.MIRROR_VIEW, Global.mirror_view) @@ -476,6 +508,16 @@ func show_hsv_configuration_popup() -> void: Global.dialog_open(true) +func select_menu_id_pressed(id : int) -> void: + match id: + SelectMenuId.SELECT_ALL: + Global.canvas.selection.select_all() + SelectMenuId.CLEAR_SELECTION: + Global.canvas.selection.clear_selection(true) + SelectMenuId.INVERT: + Global.canvas.selection.invert() + + func help_menu_id_pressed(id : int) -> void: match id: HelpMenuId.VIEW_SPLASH_SCREEN: diff --git a/src/UI/TopMenuContainer.tscn b/src/UI/TopMenuContainer.tscn index ad7ecb034..1e9b3bd1d 100644 --- a/src/UI/TopMenuContainer.tscn +++ b/src/UI/TopMenuContainer.tscn @@ -21,7 +21,7 @@ __meta__ = { [node name="FileMenu" type="MenuButton" parent="MenuItems"] margin_right = 35.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "File" switch_on_hover = true @@ -29,7 +29,7 @@ switch_on_hover = true [node name="EditMenu" type="MenuButton" parent="MenuItems"] margin_left = 39.0 margin_right = 75.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Edit" switch_on_hover = true @@ -37,7 +37,7 @@ switch_on_hover = true [node name="ViewMenu" type="MenuButton" parent="MenuItems"] margin_left = 79.0 margin_right = 121.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "View" switch_on_hover = true @@ -45,15 +45,23 @@ switch_on_hover = true [node name="ImageMenu" type="MenuButton" parent="MenuItems"] margin_left = 125.0 margin_right = 177.0 -margin_bottom = 23.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Image" switch_on_hover = true -[node name="HelpMenu" type="MenuButton" parent="MenuItems"] +[node name="SelectMenu" type="MenuButton" parent="MenuItems"] margin_left = 181.0 -margin_right = 223.0 -margin_bottom = 23.0 +margin_right = 232.0 +margin_bottom = 20.0 +mouse_default_cursor_shape = 2 +text = "Select" +switch_on_hover = true + +[node name="HelpMenu" type="MenuButton" parent="MenuItems"] +margin_left = 236.0 +margin_right = 278.0 +margin_bottom = 20.0 mouse_default_cursor_shape = 2 text = "Help" switch_on_hover = true diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index 1084e4a05..5b177614c 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -7,7 +7,6 @@ [ext_resource path="res://src/UI/TransparentChecker.tscn" type="PackedScene" id=5] [ext_resource path="res://src/UI/Canvas/Rulers/HorizontalRuler.gd" type="Script" id=6] [ext_resource path="res://src/UI/Canvas/CameraMovement.gd" type="Script" id=7] -[ext_resource path="res://src/SelectionRectangle.gd" type="Script" id=8] [ext_resource path="res://src/Shaders/TransparentChecker.shader" type="Shader" id=9] [ext_resource path="res://assets/graphics/dark_themes/tools/bucket.png" type="Texture" id=10] [ext_resource path="res://assets/graphics/dark_themes/tools/colorpicker.png" type="Texture" id=11] @@ -25,6 +24,7 @@ [ext_resource path="res://src/UI/ViewportContainer.gd" type="Script" id=23] [ext_resource path="res://assets/graphics/dark_themes/tools/rectangletool.png" type="Texture" id=24] [ext_resource path="res://assets/graphics/dark_themes/tools/ellipsetool.png" type="Texture" id=25] +[ext_resource path="res://assets/graphics/dark_themes/tools/move.png" type="Texture" id=26] [sub_resource type="ShaderMaterial" id=1] shader = ExtResource( 9 ) @@ -88,7 +88,7 @@ __meta__ = { margin_left = 7.0 margin_top = 7.0 margin_right = 39.0 -margin_bottom = 363.0 +margin_bottom = 399.0 size_flags_horizontal = 4 size_flags_vertical = 0 script = ExtResource( 1 ) @@ -113,7 +113,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="Zoom" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +[node name="Move" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] margin_top = 36.0 @@ -123,6 +123,24 @@ rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 +[node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Move"] +margin_right = 32.0 +margin_bottom = 32.0 +texture = ExtResource( 26 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Zoom" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +"UIButtons", +]] +margin_top = 72.0 +margin_right = 32.0 +margin_bottom = 104.0 +rect_min_size = Vector2( 32, 32 ) +mouse_default_cursor_shape = 2 +button_mask = 3 + [node name="TextureRect" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/Zoom"] margin_right = 32.0 margin_bottom = 32.0 @@ -134,9 +152,9 @@ __meta__ = { [node name="Pan" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 72.0 +margin_top = 108.0 margin_right = 32.0 -margin_bottom = 104.0 +margin_bottom = 140.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -152,9 +170,9 @@ __meta__ = { [node name="ColorPicker" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 108.0 +margin_top = 144.0 margin_right = 32.0 -margin_bottom = 140.0 +margin_bottom = 176.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -170,9 +188,9 @@ __meta__ = { [node name="Pencil" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 144.0 +margin_top = 180.0 margin_right = 32.0 -margin_bottom = 176.0 +margin_bottom = 212.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -188,9 +206,9 @@ __meta__ = { [node name="Eraser" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 180.0 +margin_top = 216.0 margin_right = 32.0 -margin_bottom = 212.0 +margin_bottom = 248.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -206,9 +224,9 @@ __meta__ = { [node name="Bucket" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 216.0 +margin_top = 252.0 margin_right = 32.0 -margin_bottom = 248.0 +margin_bottom = 284.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -224,9 +242,9 @@ __meta__ = { [node name="LightenDarken" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 252.0 +margin_top = 288.0 margin_right = 32.0 -margin_bottom = 284.0 +margin_bottom = 320.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -242,9 +260,9 @@ __meta__ = { [node name="RectangleTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 288.0 +margin_top = 324.0 margin_right = 32.0 -margin_bottom = 320.0 +margin_bottom = 356.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -260,9 +278,9 @@ __meta__ = { [node name="EllipseTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 324.0 +margin_top = 360.0 margin_right = 32.0 -margin_bottom = 356.0 +margin_bottom = 392.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 @@ -367,15 +385,6 @@ current = true zoom = Vector2( 0.15, 0.15 ) script = ExtResource( 7 ) -[node name="SelectionRectangle" type="Polygon2D" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer/ViewportandVerticalRuler/ViewportContainer/Viewport"] -visible = false -z_index = 1 -color = Color( 0.0823529, 0.694118, 0.623529, 0.592157 ) -invert_enable = true -invert_border = 0.5 -polygon = PoolVector2Array( 0, 0, 0, 0, 0, 0, 0, 0 ) -script = ExtResource( 8 ) - [node name="ViewportContainer2" type="ViewportContainer" parent="CanvasAndTimeline/ViewportAndRulers/HSplitContainer"] margin_left = 902.0 margin_right = 902.0