paint_control.gd 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. extends Control
  2. # A constant for whether or not we're needing to undo a shape.
  3. const UNDO_MODE_SHAPE = -2
  4. # A constant for whether or not we can undo.
  5. const UNDO_NONE = -1
  6. # How large is the image (it's actually the size of DrawingAreaBG, because that's our background canvas).
  7. const IMAGE_SIZE = Vector2(930, 720)
  8. # Enums for the various modes and brush shapes that can be applied.
  9. enum BrushModes {
  10. PENCIL,
  11. ERASER,
  12. CIRCLE_SHAPE,
  13. RECTANGLE_SHAPE,
  14. }
  15. enum BrushShapes {
  16. RECTANGLE,
  17. CIRCLE,
  18. }
  19. # The top-left position of the canvas.
  20. var TL_node
  21. # A list to hold all of the dictionaries that make up each brush.
  22. var brush_data_list = []
  23. # A boolean to hold whether or not the mouse is inside the drawing area, the mouse position last _process call
  24. # and the position of the mouse when the left mouse button was pressed.
  25. var is_mouse_in_drawing_area = false
  26. var last_mouse_pos = Vector2()
  27. var mouse_click_start_pos = null
  28. # A boolean to tell whether we've set undo_elements_list_num, which holds the size of draw_elements_list
  29. # before a new stroke is added (unless the current brush mode is 'rectangle shape' or 'circle shape', in
  30. # which case we do things a litte differently. See the undo_stroke function for more details).
  31. var undo_set = false
  32. var undo_element_list_num = -1
  33. # The current brush settings: The mode, size, color, and shape we have currently selected.
  34. var brush_mode = BrushModes.PENCIL
  35. var brush_size = 32
  36. var brush_color = Color.black
  37. var brush_shape = BrushShapes.CIRCLE;
  38. # The color of the background. We need this for the eraser (see the how we handle the eraser
  39. # in the _draw function for more details).
  40. var bg_color = Color.white
  41. func _ready():
  42. # Get the top left position node. We need this to find out whether or not the mouse is inside the canvas.
  43. TL_node = get_node("TLPos")
  44. set_process(true)
  45. func _process(_delta):
  46. var mouse_pos = get_viewport().get_mouse_position()
  47. # Check if the mouse is currently inside the canvas/drawing-area.
  48. is_mouse_in_drawing_area = false
  49. if mouse_pos.x > TL_node.global_position.x:
  50. if mouse_pos.y > TL_node.global_position.y:
  51. is_mouse_in_drawing_area = true
  52. if Input.is_mouse_button_pressed(BUTTON_LEFT):
  53. # If we do not have a position for when the mouse was first clicked, then this must
  54. # be the first time is_mouse_button_pressed has been called since the mouse button was
  55. # released, so we need to store the position.
  56. if mouse_click_start_pos == null:
  57. mouse_click_start_pos = mouse_pos
  58. # If the mouse is inside the canvas and the mouse is 1px away from the position of the mouse last _process call.
  59. if check_if_mouse_is_inside_canvas():
  60. if mouse_pos.distance_to(last_mouse_pos) >= 1:
  61. # If we are in pencil or eraser mode, then we need to draw.
  62. if brush_mode == BrushModes.PENCIL or brush_mode == BrushModes.ERASER:
  63. # If undo has not been set, meaning we've started a new stroke, then store the size of the
  64. # draw_elements_list so we can undo from this point in time.
  65. if undo_set == false:
  66. undo_set = true
  67. undo_element_list_num = brush_data_list.size()
  68. # Add the brush object to draw_elements_array.
  69. add_brush(mouse_pos, brush_mode)
  70. else:
  71. # We've finished our stroke, so we can set a new undo (if a new storke is made).
  72. undo_set = false
  73. # If the mouse is inside the canvas.
  74. if check_if_mouse_is_inside_canvas():
  75. # If we're using either the circle shape mode, or the rectangle shape mode, then
  76. # add the brush object to draw_elements_array.
  77. if brush_mode == BrushModes.CIRCLE_SHAPE or brush_mode == BrushModes.RECTANGLE_SHAPE:
  78. add_brush(mouse_pos, brush_mode)
  79. # We handle undo's differently than either pencil or eraser mode, so we need to set undo
  80. # element_list_num to -2 so we can tell if we need to undo a shape. See undo_stroke for details.
  81. undo_element_list_num = UNDO_MODE_SHAPE
  82. # Since we've released the left mouse, we need to get a new mouse_click_start_pos next time
  83. #is_mouse_button_pressed is true.
  84. mouse_click_start_pos = null
  85. # Store mouse_pos as last_mouse_pos now that we're done with _process.
  86. last_mouse_pos = mouse_pos
  87. func check_if_mouse_is_inside_canvas():
  88. # Make sure we have a mouse click starting position.
  89. if mouse_click_start_pos != null:
  90. # Make sure the mouse click starting position is inside the canvas.
  91. # This is so if we start out click outside the canvas (say chosing a color from the color picker)
  92. # and then move our mouse back into the canvas, it won't start painting.
  93. if mouse_click_start_pos.x > TL_node.global_position.x:
  94. if mouse_click_start_pos.y > TL_node.global_position.y:
  95. # Make sure the current mouse position is inside the canvas.
  96. if is_mouse_in_drawing_area:
  97. return true
  98. return false
  99. func undo_stroke():
  100. # Only undo a stroke if we have one.
  101. if undo_element_list_num == UNDO_NONE:
  102. return
  103. # If we are undoing a shape, then we can just remove the latest brush.
  104. if undo_element_list_num == UNDO_MODE_SHAPE:
  105. if brush_data_list.size() > 0:
  106. brush_data_list.remove(brush_data_list.size() - 1)
  107. # Now that we've undone a shape, we cannot undo again until another stoke is added.
  108. undo_element_list_num = UNDO_NONE
  109. # NOTE: if we only had shape brushes, then we could remove the above line and could let the user
  110. # undo until we have a empty element list.
  111. # Otherwise we're removing a either a pencil stroke or a eraser stroke.
  112. else:
  113. # Figure out how many elements/brushes we've added in the last stroke.
  114. var elements_to_remove = brush_data_list.size() - undo_element_list_num
  115. # Remove all of the elements we've added this in the last stroke.
  116. #warning-ignore:unused_variable
  117. for elment_num in range(0, elements_to_remove):
  118. brush_data_list.pop_back()
  119. # Now that we've undone a stoke, we cannot undo again until another stoke is added.
  120. undo_element_list_num = UNDO_NONE
  121. # Redraw the brushes
  122. update()
  123. func add_brush(mouse_pos, type):
  124. # Make new brush dictionary that will hold all of the data we need for the brush.
  125. var new_brush = {}
  126. # Populate the dictionary with values based on the global brush variables.
  127. # We will override these as needed if the brush is a rectange or circle.
  128. new_brush.brush_type = type
  129. new_brush.brush_pos = mouse_pos
  130. new_brush.brush_shape = brush_shape
  131. new_brush.brush_size = brush_size
  132. new_brush.brush_color = brush_color
  133. # If the new bursh is a rectangle shape, we need to calculate the top left corner of the rectangle and the
  134. # bottom right corner of the rectangle.
  135. if type == BrushModes.RECTANGLE_SHAPE:
  136. var TL_pos = Vector2()
  137. var BR_pos = Vector2()
  138. # Figure out the left and right positions of the corners and assign them to the proper variable.
  139. if mouse_pos.x < mouse_click_start_pos.x:
  140. TL_pos.x = mouse_pos.x
  141. BR_pos.x = mouse_click_start_pos.x
  142. else:
  143. TL_pos.x = mouse_click_start_pos.x
  144. BR_pos.x = mouse_pos.x
  145. # Figure out the top and bottom positions of the corners and assign them to the proper variable.
  146. if mouse_pos.y < mouse_click_start_pos.y:
  147. TL_pos.y = mouse_pos.y
  148. BR_pos.y = mouse_click_start_pos.y
  149. else:
  150. TL_pos.y = mouse_click_start_pos.y
  151. BR_pos.y = mouse_pos.y
  152. # Assign the positions to the brush.
  153. new_brush.brush_pos = TL_pos
  154. new_brush.brush_shape_rect_pos_BR = BR_pos
  155. # If the brush isa circle shape, then we need to calculate the radius of the circle.
  156. if type == BrushModes.CIRCLE_SHAPE:
  157. # Get the center point inbetween the mouse position and the position of the mouse when we clicked.
  158. var center_pos = Vector2((mouse_pos.x + mouse_click_start_pos.x) / 2, (mouse_pos.y + mouse_click_start_pos.y) / 2)
  159. # Assign the brush position to the center point, and calculate the radius of the circle using the distance from
  160. # the center to the top/bottom positon of the mouse.
  161. new_brush.brush_pos = center_pos
  162. new_brush.brush_shape_circle_radius = center_pos.distance_to(Vector2(center_pos.x, mouse_pos.y))
  163. # Add the brush and update/draw all of the brushes.
  164. brush_data_list.append(new_brush)
  165. update()
  166. func _draw():
  167. # Go through all of the brushes in brush_data_list.
  168. for brush in brush_data_list:
  169. match brush.brush_type:
  170. BrushModes.PENCIL:
  171. # If the brush shape is a rectangle, then we need to make a Rect2 so we can use draw_rect.
  172. # Draw_rect draws a rectagle at the top left corner, using the scale for the size.
  173. # So we offset the position by half of the brush size so the rectangle's center is at mouse position.
  174. if brush.brush_shape == BrushShapes.RECTANGLE:
  175. var rect = Rect2(brush.brush_pos - Vector2(brush.brush_size / 2, brush.brush_size / 2), Vector2(brush.brush_size, brush.brush_size))
  176. draw_rect(rect, brush.brush_color)
  177. # If the brush shape is a circle, then we draw a circle at the mouse position,
  178. # making the radius half of brush size (so the circle is brush size pixels in diameter).
  179. elif brush.brush_shape == BrushShapes.CIRCLE:
  180. draw_circle(brush.brush_pos, brush.brush_size / 2, brush.brush_color)
  181. BrushModes.ERASER:
  182. # NOTE: this is a really cheap way of erasing that isn't really erasing!
  183. # However, this gives similar results in a fairy simple way!
  184. # Erasing works exactly the same was as pencil does for both the rectangle shape and the circle shape,
  185. # but instead of using brush.brush_color, we instead use bg_color instead.
  186. if brush.brush_shape == BrushShapes.RECTANGLE:
  187. var rect = Rect2(brush.brush_pos - Vector2(brush.brush_size / 2, brush.brush_size / 2), Vector2(brush.brush_size, brush.brush_size))
  188. draw_rect(rect, bg_color)
  189. elif brush.brush_shape == BrushShapes.CIRCLE:
  190. draw_circle(brush.brush_pos, brush.brush_size / 2, bg_color)
  191. BrushModes.RECTANGLE_SHAPE:
  192. # We make a Rect2 with the postion at the top left. To get the size we take the bottom right position
  193. # and subtract the top left corner's position.
  194. var rect = Rect2(brush.brush_pos, brush.brush_shape_rect_pos_BR - brush.brush_pos)
  195. draw_rect(rect, brush.brush_color)
  196. BrushModes.CIRCLE_SHAPE:
  197. # We simply draw a circle using stored in brush.
  198. draw_circle(brush.brush_pos, brush.brush_shape_circle_radius, brush.brush_color)
  199. func save_picture(path):
  200. # Wait until the frame has finished before getting the texture.
  201. yield(VisualServer, "frame_post_draw")
  202. # Get the viewport image.
  203. var img = get_viewport().get_texture().get_data()
  204. # Crop the image so we only have canvas area.
  205. var cropped_image = img.get_rect(Rect2(TL_node.global_position, IMAGE_SIZE))
  206. # Flip the image on the Y-axis (it's flipped upside down by default).
  207. cropped_image.flip_y()
  208. # Save the image with the passed in path we got from the save dialog.
  209. cropped_image.save_png(path)