Przeglądaj źródła

Merge pull request #89 from TwistedTwigleg/gd_paint

Added a simple drawing demo (GD paint)
Aaron Franke 5 lat temu
rodzic
commit
8a6b1f87ba

+ 259 - 0
2d/gd_paint/PaintControl.gd

@@ -0,0 +1,259 @@
+extends Control
+
+# The TL position of the canvas
+var TL_node
+
+# A list to hold all of the dictionaries that make up each brush.
+var brush_data_list = []
+
+# A boolean to hold whether or not the mouse is inside the drawing area, the mouse position last _process call
+# and the position of the mouse when the left mouse button was pressed
+var is_mouse_in_drawing_area = false
+var last_mouse_pos = Vector2()
+var mouse_click_start_pos = null
+
+# A boolean to tell whether we've set undo_elements_list_num, which holds the size of draw_elements_list
+# before a new stroke is added (unless the current brush mode is 'rectangle shape' or 'circle shape', in
+# which case we do things a litte differently. See the undo_stroke function for more details)
+var undo_set = false
+var undo_element_list_num = -1
+
+# A constant for whether or not we're needing to undo a shape
+const UNDO_MODE_SHAPE = -2
+# A constant for whether or not we can undo
+const UNDO_NONE = -1
+
+# Enums for the various modes and brush shapes that can be applied
+enum BRUSH_MODES {
+	pencil, eraser, circle_shape, rectangle_shape
+}
+enum BRUSH_SHAPES {
+	rectangle, circle
+}
+
+# The current brush settings: The mode, size, color, and shape we have currently selected
+var brush_mode = BRUSH_MODES.pencil
+var brush_size = 32
+var brush_color = Color(1, 1, 1, 1)
+var brush_shape = BRUSH_SHAPES.circle;
+
+# The color of the background. We need this for the eraser (see the how we handle the eraser
+# in the _draw function for more details)
+var bg_color = Color(1, 1, 1, 1)
+
+# How large is the image (it's actually the size of DrawingAreaBG, because that's our background canvas)
+const IMAGE_SIZE = Vector2(930, 720)
+
+
+func _ready():
+	# Get the top left position node. We need this to find out whether or not the mouse is inside the canvas
+	TL_node = get_node("TLPos")
+	set_process(true)
+
+
+func _process(_delta):
+	var mouse_pos = get_viewport().get_mouse_position()
+	
+	# Check if the mouse is currently inside the canvas/drawing-area
+	is_mouse_in_drawing_area = false
+	if mouse_pos.x > TL_node.global_position.x:
+		if mouse_pos.y > TL_node.global_position.y:
+			is_mouse_in_drawing_area = true
+	
+	if Input.is_mouse_button_pressed(BUTTON_LEFT):
+		# If we do not have a position for when the mouse was first clicked, then this most
+		# be the first time is_mouse_button_pressed has been called since the mouse button was
+		# released, so we need to store the position
+		if mouse_click_start_pos == null:
+			mouse_click_start_pos = mouse_pos
+		
+		# If the mouse is inside the canvas and the mouse is 1px away from the position of the mouse last _process call
+		if check_if_mouse_is_inside_canvas():
+			if mouse_pos.distance_to(last_mouse_pos) >= 1:
+				# If we are in pencil or eraser mode, then we need to draw
+				if brush_mode == BRUSH_MODES.pencil or brush_mode == BRUSH_MODES.eraser:
+					# If undo has not been set, meaning we've started a new stroke, then store the size of the
+					# draw_elements_list so we can undo from this point in time
+					if undo_set == false:
+						undo_set = true
+						undo_element_list_num = brush_data_list.size()
+					# Add the brush object to draw_elements_array
+					add_brush(mouse_pos, brush_mode)
+	
+	else:
+		# We've finished our stroke, so we can set a new undo (if a new storke is made)
+		undo_set = false
+		
+		# If the mouse is inside the canvas
+		if check_if_mouse_is_inside_canvas():
+			# If we're using either the circle shape mode, or the rectangle shape mode, then
+			# add the brush object to draw_elements_array
+			if brush_mode == BRUSH_MODES.circle_shape or brush_mode == BRUSH_MODES.rectangle_shape:
+				add_brush(mouse_pos, brush_mode)
+				# We handle undo's differently than either pencil or eraser mode, so we need to set undo
+				# element_list_num to -2 so we can tell if we need to undo a shape. See undo_stroke for details
+				undo_element_list_num = UNDO_MODE_SHAPE
+		# Since we've released the left mouse, we need to get a new mouse_click_start_pos next time
+		#is_mouse_button_pressed is true.
+		mouse_click_start_pos = null
+	
+	# Store mouse_pos as last_mouse_pos now that we're done with _process
+	last_mouse_pos = mouse_pos
+
+
+func check_if_mouse_is_inside_canvas():
+	# Make sure we have a mouse click starting position
+	if mouse_click_start_pos != null:
+		# Make sure the mouse click starting position is inside the canvas.
+		# This is so if we start out click outside the canvas (say chosing a color from the color picker)
+		# and then move our mouse back into the canvas, it won't start painting
+		if mouse_click_start_pos.x > TL_node.global_position.x:
+			if mouse_click_start_pos.y > TL_node.global_position.y:
+				# Make sure the current mouse position is inside the canvas
+				if is_mouse_in_drawing_area == true:
+					return true
+	return false
+
+
+func undo_stroke():
+	# Only undo a stroke if we have one
+	if undo_element_list_num == UNDO_NONE:
+		return
+	
+	# If we are undoing a shape, then we can just remove the latest brush
+	if undo_element_list_num == UNDO_MODE_SHAPE:
+		if brush_data_list.size() > 0:
+			brush_data_list.remove(brush_data_list.size() - 1)
+		
+		# Now that we've undone a shape, we cannot undo again until another stoke is added
+		undo_element_list_num = UNDO_NONE
+		# NOTE: if we only had shape brushes, then we could remove the above line and could let the user
+		# undo until we have a empty element list
+	
+	# Otherwise we're removing a either a pencil stroke or a eraser stroke.
+	else:
+		# Figure out how many elements/brushes we've added in the last stroke
+		var elements_to_remove = brush_data_list.size() - undo_element_list_num
+		# Remove all of the elements we've added this in the last stroke
+		#warning-ignore:unused_variable
+		for elment_num in range(0, elements_to_remove):
+			brush_data_list.pop_back()
+		
+		# Now that we've undone a stoke, we cannot undo again until another stoke is added
+		undo_element_list_num = UNDO_NONE
+
+	# Redraw the brushes
+	update()
+
+
+func add_brush(mouse_pos, type):
+	# Make new brush dictionary that will hold all of the data we need for the brush.
+	var new_brush = {}
+	
+	# Populate the dictionary with values based on the global brush variables.
+	# We will override these as needed if the brush is a rectange or circle.
+	new_brush.brush_type = type
+	new_brush.brush_pos = mouse_pos
+	new_brush.brush_shape = brush_shape
+	new_brush.brush_size = brush_size
+	new_brush.brush_color = brush_color
+	
+	# If the new bursh is a rectangle shape, we need to calculate the top left corner of the rectangle and the
+	# bottom right corner of the rectangle
+	if type == BRUSH_MODES.rectangle_shape:
+		var TL_pos = Vector2()
+		var BR_pos = Vector2()
+		
+		# Figure out the left and right positions of the corners and assign them to the proper variable
+		if mouse_pos.x < mouse_click_start_pos.x:
+			TL_pos.x = mouse_pos.x
+			BR_pos.x = mouse_click_start_pos.x
+		else:
+			TL_pos.x = mouse_click_start_pos.x
+			BR_pos.x = mouse_pos.x
+		
+		# Figure out the top and bottom positions of the corners and assign them to the proper variable
+		if mouse_pos.y < mouse_click_start_pos.y:
+			TL_pos.y = mouse_pos.y
+			BR_pos.y = mouse_click_start_pos.y
+		else:
+			TL_pos.y = mouse_click_start_pos.y
+			BR_pos.y = mouse_pos.y
+		
+		# Assign the positions to the brush
+		new_brush.brush_pos = TL_pos
+		new_brush.brush_shape_rect_pos_BR = BR_pos
+	
+	# If the brush isa circle shape, then we need to calculate the radius of the circle
+	if type == BRUSH_MODES.circle_shape:
+		# Get the center point inbetween the mouse position and the position of the mouse when we clicked
+		var center_pos = Vector2((mouse_pos.x + mouse_click_start_pos.x) / 2, (mouse_pos.y + mouse_click_start_pos.y) / 2)
+		# Assign the brush position to the center point, and calculate the radius of the circle using the distance from
+		# the center to the top/bottom positon of the mouse
+		new_brush.brush_pos = center_pos
+		new_brush.brush_shape_circle_radius = center_pos.distance_to(Vector2(center_pos.x, mouse_pos.y))
+	
+	# Add the brush and update/draw all of the brushes
+	brush_data_list.append(new_brush)
+	update()
+
+
+func _draw():
+	# Go through all of the brushes in brush_data_list
+	for brush in brush_data_list:
+		
+		# If the brush is a pencil
+		if brush.brush_type == BRUSH_MODES.pencil:
+			# If the brush shape is a rectangle, then we need to make a Rect2 so we can use draw_rect.
+			# Draw_rect draws a rectagle at the top left corner, using the scale for the size.
+			# So we offset the position by half of the brush size so the rectangle's center is at mouse position
+			if brush.brush_shape == BRUSH_SHAPES.rectangle:
+				var rect = Rect2(brush.brush_pos - Vector2(brush.brush_size / 2, brush.brush_size / 2), Vector2(brush.brush_size, brush.brush_size))
+				draw_rect(rect, brush.brush_color)
+			# If the brush shape is a circle, then we draw a circle at the mouse position,
+			# making the radius half of brush size (so the circle is brush size pixels in diameter)
+			elif brush.brush_shape == BRUSH_SHAPES.circle:
+				draw_circle(brush.brush_pos, brush.brush_size / 2, brush.brush_color)
+		
+		# If the brush is a eraser
+		elif brush.brush_type == BRUSH_MODES.eraser:
+			# NOTE: this is a really cheap way of erasing that isn't really erasing!
+			# However, this gives similar results in a fairy simple way!
+			
+			# Erasing works exactly the same was as pencil does for both the rectangle shape and the circle shape,
+			# but instead of using brush.brush_color, we instead use bg_color instead.
+			if brush.brush_shape == BRUSH_SHAPES.rectangle:
+				var rect = Rect2(brush.brush_pos - Vector2(brush.brush_size / 2, brush.brush_size / 2), Vector2(brush.brush_size, brush.brush_size))
+				draw_rect(rect, bg_color)
+			elif brush.brush_shape == BRUSH_SHAPES.circle:
+				draw_circle(brush.brush_pos, brush.brush_size / 2, bg_color)
+		
+		# If the brush is a rectangle shape
+		elif brush.brush_type == BRUSH_MODES.rectangle_shape:
+			# We make a Rect2 with the postion at the top left. To get the size we take the bottom right position
+			# and subtract the top left corner's position
+			var rect = Rect2(brush.brush_pos, brush.brush_shape_rect_pos_BR - brush.brush_pos)
+			draw_rect(rect, brush.brush_color)
+		
+		# If the brush is a circle shape
+		elif brush.brush_type == BRUSH_MODES.circle_shape:
+			# We simply draw a circle using stored in brush
+			draw_circle(brush.brush_pos, brush.brush_shape_circle_radius, brush.brush_color)
+
+
+
+func save_picture(path):
+	# Wait a couple frames so the save dialog isn't in the way
+	yield (get_tree(), "idle_frame")
+	yield (get_tree(), "idle_frame")
+	
+	# Get the viewport image
+	var img = get_viewport().get_texture().get_data()
+	# Crop the image so we only have canvas area.
+	var cropped_image = img.get_rect(Rect2(TL_node.global_position, IMAGE_SIZE))
+	# Flip the image on the Y-axis (it's flipped upside down by default)
+	cropped_image.flip_y()
+	
+	# Save the image with the passed in path we got from the save dialog
+	cropped_image.save_png(path)
+

BIN
2d/gd_paint/PaintTools.png


+ 221 - 0
2d/gd_paint/Paint_root.tscn

@@ -0,0 +1,221 @@
+[gd_scene load_steps=5 format=2]
+
+[ext_resource path="res://PaintControl.gd" type="Script" id=1]
+[ext_resource path="res://ToolsPanel.gd" type="Script" id=2]
+[ext_resource path="res://PaintTools.png" type="Texture" id=3]
+
+[sub_resource type="StyleBoxFlat" id=1]
+bg_color = Color( 1, 1, 1, 1 )
+
+[node name="PaintRoot" type="Control"]
+margin_right = 40.0
+margin_bottom = 40.0
+
+[node name="DrawingAreaBG" type="Panel" parent="."]
+margin_left = 350.0
+margin_right = 1280.0
+margin_bottom = 720.0
+custom_styles/panelf = SubResource( 1 )
+custom_styles/panel = SubResource( 1 )
+custom_styles/panelnc = SubResource( 1 )
+
+[node name="PaintControl" type="Control" parent="."]
+editor/display_folded = true
+margin_right = 40.0
+margin_bottom = 40.0
+script = ExtResource( 1 )
+
+[node name="TLPos" type="Position2D" parent="PaintControl"]
+position = Vector2( 350, 0 )
+
+[node name="ToolsPanel" type="Panel" parent="."]
+editor/display_folded = true
+margin_right = 350.0
+margin_bottom = 720.0
+script = ExtResource( 2 )
+
+[node name="LabelTools" type="Label" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 10.0
+margin_right = 330.0
+margin_bottom = 24.0
+text = "Selected tool: pencil"
+align = 1
+
+[node name="ButtonToolPencil" type="Button" parent="ToolsPanel"]
+editor/display_folded = true
+margin_left = 40.0
+margin_top = 40.0
+margin_right = 100.0
+margin_bottom = 100.0
+
+[node name="Sprite" type="Sprite" parent="ToolsPanel/ButtonToolPencil"]
+position = Vector2( 30, 30 )
+scale = Vector2( 2.5, 2.5 )
+texture = ExtResource( 3 )
+region_enabled = true
+region_rect = Rect2( 0, 0, 16, 16 )
+
+[node name="ButtonToolEraser" type="Button" parent="ToolsPanel"]
+editor/display_folded = true
+margin_left = 110.0
+margin_top = 40.0
+margin_right = 170.0
+margin_bottom = 100.0
+
+[node name="Sprite" type="Sprite" parent="ToolsPanel/ButtonToolEraser"]
+position = Vector2( 30, 30 )
+scale = Vector2( 2.5, 2.5 )
+texture = ExtResource( 3 )
+region_enabled = true
+region_rect = Rect2( 16, 0, 16, 16 )
+
+[node name="ButtonToolRectangle" type="Button" parent="ToolsPanel"]
+editor/display_folded = true
+margin_left = 180.0
+margin_top = 40.0
+margin_right = 240.0
+margin_bottom = 100.0
+
+[node name="Sprite" type="Sprite" parent="ToolsPanel/ButtonToolRectangle"]
+position = Vector2( 30, 30 )
+scale = Vector2( 2.5, 2.5 )
+texture = ExtResource( 3 )
+region_enabled = true
+region_rect = Rect2( 0, 16, 16, 16 )
+
+[node name="ButtonToolCircle" type="Button" parent="ToolsPanel"]
+editor/display_folded = true
+margin_left = 250.0
+margin_top = 40.0
+margin_right = 310.0
+margin_bottom = 100.0
+
+[node name="Sprite" type="Sprite" parent="ToolsPanel/ButtonToolCircle"]
+position = Vector2( 30, 30 )
+scale = Vector2( 2.5, 2.5 )
+texture = ExtResource( 3 )
+region_enabled = true
+region_rect = Rect2( 16, 16, 16, 16 )
+
+[node name="LabelBrushColor" type="Label" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 120.0
+margin_right = 330.0
+margin_bottom = 134.0
+text = "Current color"
+align = 1
+
+[node name="ColorPickerBrush" type="ColorPickerButton" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 140.0
+margin_right = 330.0
+margin_bottom = 190.0
+color = Color( 1, 1, 1, 1 )
+
+[node name="LabelBrushSize" type="Label" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 210.0
+margin_right = 330.0
+margin_bottom = 224.0
+text = "Brush size: 32px"
+align = 1
+
+[node name="HScrollBarBrushSize" type="HScrollBar" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 230.0
+margin_right = 330.0
+margin_bottom = 260.0
+min_value = 1.0
+value = 32.0
+
+[node name="LabelBrushShape" type="Label" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 280.0
+margin_right = 330.0
+margin_bottom = 294.0
+text = "Brush shape: circle"
+align = 1
+
+[node name="ButtonShapeBox" type="Button" parent="ToolsPanel"]
+editor/display_folded = true
+margin_left = 100.0
+margin_top = 300.0
+margin_right = 160.0
+margin_bottom = 360.0
+
+[node name="Sprite" type="Sprite" parent="ToolsPanel/ButtonShapeBox"]
+position = Vector2( 30, 30 )
+scale = Vector2( 2.5, 2.5 )
+texture = ExtResource( 3 )
+region_enabled = true
+region_rect = Rect2( 0, 16, 16, 16 )
+
+[node name="ButtonShapeCircle" type="Button" parent="ToolsPanel"]
+editor/display_folded = true
+margin_left = 190.0
+margin_top = 300.0
+margin_right = 250.0
+margin_bottom = 360.0
+
+[node name="Sprite" type="Sprite" parent="ToolsPanel/ButtonShapeCircle"]
+position = Vector2( 30, 30 )
+scale = Vector2( 2.5, 2.5 )
+texture = ExtResource( 3 )
+region_enabled = true
+region_rect = Rect2( 16, 16, 16, 16 )
+
+[node name="LabelBackgroundColor" type="Label" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 400.0
+margin_right = 330.0
+margin_bottom = 414.0
+text = "Background color"
+align = 1
+
+[node name="ColorPickerBackground" type="ColorPickerButton" parent="ToolsPanel"]
+margin_left = 20.0
+margin_top = 420.0
+margin_right = 330.0
+margin_bottom = 470.0
+color = Color( 1, 1, 1, 1 )
+edit_alpha = false
+
+[node name="LabelStats" type="Label" parent="ToolsPanel"]
+modulate = Color( 0.414062, 0.414062, 0.414062, 1 )
+margin_left = 20.0
+margin_top = 590.0
+margin_right = 330.0
+margin_bottom = 604.0
+text = "Brush objects: 00000"
+align = 1
+
+[node name="ButtonUndo" type="Button" parent="ToolsPanel"]
+margin_left = 10.0
+margin_top = 520.0
+margin_right = 340.0
+margin_bottom = 560.0
+text = "Undo last stroke"
+
+[node name="ButtonSave" type="Button" parent="ToolsPanel"]
+margin_left = 10.0
+margin_top = 620.0
+margin_right = 340.0
+margin_bottom = 660.0
+text = "Save picture"
+
+[node name="ButtonClear" type="Button" parent="ToolsPanel"]
+margin_left = 10.0
+margin_top = 670.0
+margin_right = 340.0
+margin_bottom = 710.0
+text = "Clear picture"
+
+[node name="SaveFileDialog" type="FileDialog" parent="."]
+margin_right = 600.0
+margin_bottom = 400.0
+resizable = true
+access = 2
+filters = PoolStringArray( "*.png" )
+current_dir = "/home/ubuntu_noah/Documents/New_2019_GitHub_Repositories/godot-demo-projects/2d/gd_paint"
+current_path = "/home/ubuntu_noah/Documents/New_2019_GitHub_Repositories/godot-demo-projects/2d/gd_paint/"

+ 120 - 0
2d/gd_paint/ToolsPanel.gd

@@ -0,0 +1,120 @@
+extends Panel
+
+
+var paint_control
+
+var label_tools
+var label_brush_size
+var label_brush_shape
+var label_stats
+
+var save_dialog
+
+
+func _ready():
+	# Get PaintControl and SaveFileDialog
+	paint_control = get_parent().get_node("PaintControl")
+	save_dialog = get_parent().get_node("SaveFileDialog")
+
+	# warning-ignore-all:return_value_discarded
+	# Assign all of the needed signals for the oppersation buttons
+	get_node("ButtonUndo").connect("pressed", self, "button_pressed", ["undo_stroke"])
+	get_node("ButtonSave").connect("pressed", self, "button_pressed", ["save_picture"])
+	get_node("ButtonClear").connect("pressed", self, "button_pressed", ["clear_picture"])
+
+	# Assign all of the needed signals for the brush buttons
+	get_node("ButtonToolPencil").connect("pressed", self, "button_pressed", ["mode_pencil"])
+	get_node("ButtonToolEraser").connect("pressed", self, "button_pressed", ["mode_eraser"])
+	get_node("ButtonToolRectangle").connect("pressed", self, "button_pressed", ["mode_rectangle"])
+	get_node("ButtonToolCircle").connect("pressed", self, "button_pressed", ["mode_circle"])
+	get_node("ButtonShapeBox").connect("pressed", self, "button_pressed", ["shape_rectangle"])
+	get_node("ButtonShapeCircle").connect("pressed", self, "button_pressed", ["shape_circle"])
+
+	# Assign all of the needed signals for the other brush settings (and ColorPickerBackground)
+	get_node("ColorPickerBrush").connect("color_changed", self, "brush_color_changed")
+	get_node("ColorPickerBackground").connect("color_changed", self, "background_color_changed")
+	get_node("HScrollBarBrushSize").connect("value_changed", self, "brush_size_changed")
+
+	# Assign the 'file_selected' signal in SaveFileDialog
+	save_dialog.connect("file_selected", self, "save_file_selected")
+
+	# Get all of the labels so we can update them when settings change
+	label_tools = get_node("LabelTools")
+	label_brush_size = get_node("LabelBrushSize")
+	label_brush_shape = get_node("LabelBrushShape")
+	label_stats = get_node("LabelStats")
+
+	# Set physics process so we can update the status label
+	set_physics_process(true)
+
+
+func _physics_process(_delta):
+	# Update the status label with the newest brush element count
+	label_stats.text = "Brush objects: " + String(paint_control.brush_data_list.size())
+
+
+func button_pressed(button_name):
+	# If a brush mode button is pressed
+	var tool_name = null
+	var shape_name = null
+	
+	if button_name == "mode_pencil":
+		paint_control.brush_mode = paint_control.BRUSH_MODES.pencil
+		tool_name = "pencil"
+	elif button_name == "mode_eraser":
+		paint_control.brush_mode = paint_control.BRUSH_MODES.eraser
+		tool_name = "eraser"
+	elif button_name == "mode_rectangle":
+		paint_control.brush_mode = paint_control.BRUSH_MODES.rectangle_shape
+		tool_name = "rectangle shape"
+	elif button_name == "mode_circle":
+		paint_control.brush_mode = paint_control.BRUSH_MODES.circle_shape
+		tool_name = "circle shape"
+
+	# If a brush shape button is pressed
+	elif button_name == "shape_rectangle":
+		paint_control.brush_shape = paint_control.BRUSH_SHAPES.rectangle
+		shape_name = "rectangle"
+	elif button_name == "shape_circle":
+		paint_control.brush_shape = paint_control.BRUSH_SHAPES.circle
+		shape_name = "circle";
+
+	# If a opperation button is pressed
+	elif button_name == "clear_picture":
+		paint_control.brush_data_list = []
+		paint_control.update()
+	elif button_name == "save_picture":
+		save_dialog.popup_centered()
+	elif button_name == "undo_stroke":
+		paint_control.undo_stroke()
+	
+	# Update the labels (in case the brush mode or brush shape has changed)
+	if tool_name != null:
+		label_tools.text = "Selected tool: " + tool_name
+	if shape_name != null:
+		label_brush_shape.text = "Brush shape: " + shape_name
+
+
+func brush_color_changed(color):
+	# Change the brush color to whatever color the color picker is
+	paint_control.brush_color = color
+
+
+func background_color_changed(color):
+	# Change the background color to whatever colorthe background color picker is
+	get_parent().get_node("DrawingAreaBG").modulate = color
+	paint_control.bg_color = color
+	# Because of how the eraser works we also need to redraw the paint control
+	paint_control.update()
+
+
+func brush_size_changed(value):
+	# Change the size of the brush, and update the label to reflect the new value
+	paint_control.brush_size = ceil(value)
+	label_brush_size.text = "Brush size: " + String(ceil(value)) + "px"
+
+
+func save_file_selected(path):
+	# Call save_picture in paint_control, passing in the path we recieved from SaveFileDialog
+	paint_control.save_picture(path)
+

+ 14 - 0
2d/gd_paint/default_env.tres

@@ -0,0 +1,14 @@
+[gd_resource type="Environment" load_steps=2 format=2]
+
+[sub_resource type="ProceduralSky" id=1]
+sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 )
+sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 )
+sky_curve = 0.25
+ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 )
+ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 )
+ground_curve = 0.01
+sun_energy = 16.0
+
+[resource]
+background_mode = 2
+background_sky = SubResource( 1 )

BIN
2d/gd_paint/icon.png


+ 35 - 0
2d/gd_paint/project.godot

@@ -0,0 +1,35 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+;   [section] ; section goes between []
+;   param=value ; assign values to parameters
+
+config_version=4
+
+_global_script_classes=[  ]
+_global_script_class_icons={
+
+}
+
+[application]
+
+config/name="GD Paint"
+run/main_scene="res://Paint_root.tscn"
+config/icon="res://icon.png"
+
+[display]
+
+window/size/width=1280
+window/size/height=720
+window/stretch/mode="2d"
+window/stretch/aspect="keep"
+
+[gdnative]
+
+singletons=[  ]
+
+[rendering]
+
+environment/default_environment="res://default_env.tres"