diff --git a/Translations/Translations.pot b/Translations/Translations.pot index 66bc62338..6a64b7dcf 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -97,6 +97,15 @@ msgstr "" msgid "Redo" msgstr "" +msgid "Copy" +msgstr "" + +msgid "Paste" +msgstr "" + +msgid "Delete" +msgstr "" + msgid "Scale Image" msgstr "" diff --git a/project.godot b/project.godot index 8c4a5739f..8283cc227 100644 --- a/project.godot +++ b/project.godot @@ -14,6 +14,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Classes/AnimationTag.gd" }, { +"base": "Popup", +"class": "Brushes", +"language": "GDScript", +"path": "res://src/UI/BrushesPopup.gd" +}, { "base": "Node2D", "class": "Canvas", "language": "GDScript", @@ -59,6 +64,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://src/Palette/PaletteColor.gd" }, { +"base": "PopupPanel", +"class": "Patterns", +"language": "GDScript", +"path": "res://src/UI/PatternsPopup.gd" +}, { "base": "Reference", "class": "Project", "language": "GDScript", @@ -66,6 +76,7 @@ _global_script_classes=[ { } ] _global_script_class_icons={ "AnimationTag": "", +"Brushes": "", "Canvas": "", "Cel": "", "Drawer": "", @@ -75,6 +86,7 @@ _global_script_class_icons={ "LayerButton": "", "Palette": "", "PaletteColor": "", +"Patterns": "", "Project": "" } @@ -97,6 +109,7 @@ Global="*res://src/Autoload/Global.gd" Import="*res://src/Autoload/Import.gd" OpenSave="*res://src/Autoload/OpenSave.gd" DrawingAlgos="*res://src/Autoload/DrawingAlgos.gd" +Tools="*res://src/Autoload/Tools.gd" Html5FileExchange="*res://src/Autoload/HTML5FileExchange.gd" [debug] diff --git a/src/Autoload/DrawingAlgos.gd b/src/Autoload/DrawingAlgos.gd index 0bd7dad76..55e558a98 100644 --- a/src/Autoload/DrawingAlgos.gd +++ b/src/Autoload/DrawingAlgos.gd @@ -1,336 +1,6 @@ extends Node -var drawer := Drawer.new() -var mouse_press_pixels := [] # Cleared after mouse release -var mouse_press_pressure_values := [] # Cleared after mouse release - - -func reset() -> void: - drawer.reset() - mouse_press_pixels.clear() - mouse_press_pressure_values.clear() - - -func draw_pixel_blended(sprite : Image, pos : Vector2, color : Color, pen_pressure : float, current_mouse_button := -1, current_action := -1) -> void: - var x_min = Global.current_project.x_min - var x_max = Global.current_project.x_max - var y_min = Global.current_project.y_min - var y_max = Global.current_project.y_max - - # Check if Tiling is enabled and whether mouse is in TilingPreviews - if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)): - pos = pos.posmodv(Global.current_project.size) - - if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)): - return - - var pos_floored := pos.floor() - var current_pixel_color = sprite.get_pixelv(pos) - var saved_pixel_index := mouse_press_pixels.find(pos_floored) - if current_action == Global.Tools.PENCIL && color.a < 1: - color = blend_colors(color, current_pixel_color) - - if current_pixel_color != color && (saved_pixel_index == -1 || pen_pressure > mouse_press_pressure_values[saved_pixel_index]): - if current_action == Global.Tools.LIGHTENDARKEN: - var ld : int = Global.ld_modes[current_mouse_button] - var ld_amount : float = Global.ld_amounts[current_mouse_button] - if ld == Global.Lighten_Darken_Mode.LIGHTEN: - color = current_pixel_color.lightened(ld_amount) - else: - color = current_pixel_color.darkened(ld_amount) - - if saved_pixel_index == -1: - mouse_press_pixels.append(pos_floored) - mouse_press_pressure_values.append(pen_pressure) - else: - mouse_press_pressure_values[saved_pixel_index] = pen_pressure - drawer.set_pixel(sprite, pos, color) - - -func draw_brush(sprite : Image, pos : Vector2, color : Color, current_mouse_button : int, pen_pressure : float, current_action := -1) -> void: - if Global.can_draw && Global.has_focus: - var x_min = Global.current_project.x_min - var x_max = Global.current_project.x_max - var y_min = Global.current_project.y_min - var y_max = Global.current_project.y_max - - if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.ALPHA: - if current_action == Global.Tools.PENCIL: - color.a *= pen_pressure - elif current_action == Global.Tools.ERASER: # This is not working - color.a *= (1.0 - pen_pressure) - - var brush_size : int = Global.brush_sizes[current_mouse_button] - var brush_type : int = Global.current_brush_types[current_mouse_button] - - var horizontal_mirror : bool = Global.horizontal_mirror[current_mouse_button] - var vertical_mirror : bool = Global.vertical_mirror[current_mouse_button] - var pixel_perfect : bool = Global.pixel_perfect[current_mouse_button] - - drawer.pixel_perfect = pixel_perfect if brush_size == 1 else false - drawer.h_mirror = horizontal_mirror - drawer.v_mirror = vertical_mirror - - if brush_type == Global.Brush_Types.PIXEL || current_action == Global.Tools.LIGHTENDARKEN: - var start_pos_x = floor(pos.x - (brush_size >> 1)) - var start_pos_y = floor(pos.y - (brush_size >> 1)) - var end_pos_x = floor(start_pos_x + brush_size) - var end_pos_y = floor(start_pos_y + brush_size) - - for cur_pos_x in range(start_pos_x, end_pos_x): - for cur_pos_y in range(start_pos_y, end_pos_y): - draw_pixel_blended(sprite, Vector2(cur_pos_x, cur_pos_y), color, pen_pressure, current_mouse_button, current_action) - Global.canvas.sprite_changed_this_frame = true - - elif brush_type == Global.Brush_Types.CIRCLE || brush_type == Global.Brush_Types.FILLED_CIRCLE: - plot_circle(sprite, pos.x, pos.y, brush_size, color, pen_pressure, brush_type == Global.Brush_Types.FILLED_CIRCLE) - Global.canvas.sprite_changed_this_frame = true - - else: - var brush_index : int = Global.custom_brush_indexes[current_mouse_button] - var custom_brush_image : Image - if brush_type != Global.Brush_Types.RANDOM_FILE: - custom_brush_image = Global.brush_images[current_mouse_button] - else: # Handle random brush - var brush_button = Global.file_brush_container.get_child(brush_index + 3) - var random_index = randi() % brush_button.random_brushes.size() - custom_brush_image = Image.new() - custom_brush_image.copy_from(brush_button.random_brushes[random_index]) - var custom_brush_size = custom_brush_image.get_size() - custom_brush_image.resize(custom_brush_size.x * brush_size, custom_brush_size.y * brush_size, Image.INTERPOLATE_NEAREST) - custom_brush_image = Global.blend_image_with_color(custom_brush_image, color, Global.interpolate_spinboxes[current_mouse_button].value / 100) - custom_brush_image.lock() - - var custom_brush_size := custom_brush_image.get_size() - Vector2.ONE - pos = pos.floor() - # #Check if Tiling is enabled and whether mouse is in TilingPreviews - if Global.tile_mode and point_in_rectangle(pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)): - pos = pos.posmodv(Global.current_project.size) - - var dst := rectangle_center(pos, custom_brush_size) - var src_rect := Rect2(Vector2.ZERO, custom_brush_size + Vector2.ONE) - # Rectangle with the same size as the brush, but at cursor's position - var pos_rect := Rect2(dst, custom_brush_size + Vector2.ONE) - - # The selection rectangle - # If there's no rectangle, the whole canvas is considered a selection - var selection_rect := Rect2() - selection_rect.position = Vector2(x_min, y_min) - selection_rect.end = Vector2(x_max, y_max) - # Intersection of the position rectangle and selection - var pos_rect_clipped := pos_rect.clip(selection_rect) - # If the size is 0, that means that the brush wasn't positioned inside the selection - if pos_rect_clipped.size == Vector2.ZERO: - return - - # Re-position src_rect and dst based on the clipped position - var pos_difference := (pos_rect.position - pos_rect_clipped.position).abs() - # Obviously, if pos_rect and pos_rect_clipped are the same, pos_difference is Vector2.ZERO - src_rect.position = pos_difference - dst += pos_difference - src_rect.end -= pos_rect.end - pos_rect_clipped.end - # If the selection rectangle is smaller than the brush, ... - # ... make sure pixels aren't being drawn outside the selection by adjusting src_rect's size - src_rect.size.x = min(src_rect.size.x, selection_rect.size.x) - src_rect.size.y = min(src_rect.size.y, selection_rect.size.y) - - # Handle mirroring - var mirror_x = x_max + x_min - pos.x - (pos.x - dst.x) - var mirror_y = y_max + y_min - pos.y - (pos.y - dst.y) - if int(pos_rect_clipped.size.x) % 2 != 0: - mirror_x -= 1 - if int(pos_rect_clipped.size.y) % 2 != 0: - mirror_y -= 1 - # Use custom blend function cause of godot's issue #31124 - if color.a > 0: # If it's the pencil - sprite.blend_rect(custom_brush_image, src_rect, dst) - if horizontal_mirror: - sprite.blend_rect(custom_brush_image, src_rect, Vector2(mirror_x, dst.y)) - if vertical_mirror: - sprite.blend_rect(custom_brush_image, src_rect, Vector2(dst.x, mirror_y)) - if horizontal_mirror && vertical_mirror: - sprite.blend_rect(custom_brush_image, src_rect, Vector2(mirror_x, mirror_y)) - - else: # if it's transparent - if it's the eraser - var custom_brush := Image.new() - if brush_type == Global.Brush_Types.CUSTOM: - custom_brush.copy_from(Global.current_project.brushes[brush_index]) - else: - custom_brush.copy_from(Global.file_brushes[brush_index]) - custom_brush_size = custom_brush.get_size() - custom_brush.resize(custom_brush_size.x * brush_size, custom_brush_size.y * brush_size, Image.INTERPOLATE_NEAREST) - var custom_brush_blended = Global.blend_image_with_color(custom_brush, color, 1) - - sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, dst) - if horizontal_mirror: - sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(mirror_x, dst.y)) - if vertical_mirror: - sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(dst.x, mirror_y)) - if horizontal_mirror && vertical_mirror: - sprite.blit_rect_mask(custom_brush_blended, custom_brush, src_rect, Vector2(mirror_x, mirror_y)) - - sprite.lock() - Global.canvas.sprite_changed_this_frame = true - - Global.canvas.previous_mouse_pos_for_lines = pos.floor() + Vector2(0.5, 0.5) - Global.canvas.previous_mouse_pos_for_lines.x = clamp(Global.canvas.previous_mouse_pos_for_lines.x, Global.canvas.location.x, Global.canvas.location.x + Global.current_project.size.x) - Global.canvas.previous_mouse_pos_for_lines.y = clamp(Global.canvas.previous_mouse_pos_for_lines.y, Global.canvas.location.y, Global.canvas.location.y + Global.current_project.size.y) - if Global.canvas.is_making_line: - Global.canvas.line_pos[0] = Global.canvas.previous_mouse_pos_for_lines - - -# Bresenham's Algorithm -# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency -func fill_gaps(sprite : Image, end_pos : Vector2, start_pos : Vector2, color : Color, current_mouse_button : int, pen_pressure : float, current_action := -1) -> void: - var previous_mouse_pos_floored = start_pos.floor() - var mouse_pos_floored = end_pos.floor() - var dx := int(abs(mouse_pos_floored.x - previous_mouse_pos_floored.x)) - var dy := int(-abs(mouse_pos_floored.y - previous_mouse_pos_floored.y)) - var err := dx + dy - var e2 := err << 1 # err * 2 - var sx = 1 if previous_mouse_pos_floored.x < mouse_pos_floored.x else -1 - var sy = 1 if previous_mouse_pos_floored.y < mouse_pos_floored.y else -1 - var x = previous_mouse_pos_floored.x - var y = previous_mouse_pos_floored.y - while !(x == mouse_pos_floored.x && y == mouse_pos_floored.y): - draw_brush(sprite, Vector2(x, y), color, current_mouse_button, pen_pressure, current_action) - e2 = err << 1 - if e2 >= dy: - err += dy - x += sx - if e2 <= dx: - err += dx - y += sy - - -# Algorithm based on http://members.chello.at/easyfilter/bresenham.html -func plot_circle(sprite : Image, xm : int, ym : int, r : int, color : Color, pen_pressure : float, fill := false) -> void: - var radius := r # Used later for filling - var x := -r - var y := 0 - var err := 2 - r * 2 # II. Quadrant - while x < 0: - var quadrant_1 := Vector2(xm - x, ym + y) - var quadrant_2 := Vector2(xm - y, ym - x) - var quadrant_3 := Vector2(xm + x, ym - y) - var quadrant_4 := Vector2(xm + y, ym + x) - draw_pixel_blended(sprite, quadrant_1, color, pen_pressure) - draw_pixel_blended(sprite, quadrant_2, color, pen_pressure) - draw_pixel_blended(sprite, quadrant_3, color, pen_pressure) - draw_pixel_blended(sprite, quadrant_4, color, pen_pressure) - - r = err - if r <= y: - y += 1 - err += y * 2 + 1 - if r > x || err > y: - x += 1 - err += x * 2 + 1 - - if fill: - for j in range (-radius, radius + 1): - for i in range (-radius, radius + 1): - if i * i + j * j <= radius * radius: - var draw_pos := Vector2(i + xm, j + ym) - draw_pixel_blended(sprite, draw_pos, color, Global.canvas.pen_pressure) - - -# Thanks to https://en.wikipedia.org/wiki/Flood_fill -func flood_fill(sprite : Image, pos : Vector2, target_color : Color, replace_color : Color) -> void: - var x_min = Global.current_project.x_min - var x_max = Global.current_project.x_max - var y_min = Global.current_project.y_min - var y_max = Global.current_project.y_max - pos = pos.floor() - var pixel = sprite.get_pixelv(pos) - if target_color == replace_color: - return - elif pixel != target_color: - return - else: - - if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)): - return - - var q = [pos] - for n in q: - # If the difference in colors is very small, break the loop (thanks @azagaya on GitHub!) - if target_color == replace_color: - break - var west : Vector2 = n - var east : Vector2 = n - while west.x >= x_min && sprite.get_pixelv(west) == target_color: - west += Vector2.LEFT - while east.x < x_max && sprite.get_pixelv(east) == target_color: - east += Vector2.RIGHT - for px in range(west.x + 1, east.x): - var p := Vector2(px, n.y) - # Draw - sprite.set_pixelv(p, replace_color) - replace_color = sprite.get_pixelv(p) - var north := p + Vector2.UP - var south := p + Vector2.DOWN - if north.y >= y_min && sprite.get_pixelv(north) == target_color: - q.append(north) - if south.y < y_max && sprite.get_pixelv(south) == target_color: - q.append(south) - - Global.canvas.sprite_changed_this_frame = true - - -func pattern_fill(sprite : Image, pos : Vector2, pattern : Image, target_color : Color, var offset : Vector2) -> void: - var x_min = Global.current_project.x_min - var x_max = Global.current_project.x_max - var y_min = Global.current_project.y_min - var y_max = Global.current_project.y_max - pos = pos.floor() - if !point_in_rectangle(pos, Vector2(x_min - 1, y_min - 1), Vector2(x_max, y_max)): - return - - pattern.lock() - var pattern_size := pattern.get_size() - var q = [pos] - - for n in q: - var west : Vector2 = n - var east : Vector2 = n - while west.x >= x_min && sprite.get_pixelv(west) == target_color: - west += Vector2.LEFT - while east.x < x_max && sprite.get_pixelv(east) == target_color: - east += Vector2.RIGHT - - for px in range(west.x + 1, east.x): - var p := Vector2(px, n.y) - var xx : int = int(px + offset.x) % int(pattern_size.x) - var yy : int = int(n.y + offset.y) % int(pattern_size.y) - var pattern_color : Color = pattern.get_pixel(xx, yy) - if pattern_color == target_color: - continue - sprite.set_pixelv(p, pattern_color) - - var north := p + Vector2.UP - var south := p + Vector2.DOWN - if north.y >= y_min && sprite.get_pixelv(north) == target_color: - q.append(north) - if south.y < y_max && sprite.get_pixelv(south) == target_color: - q.append(south) - - pattern.unlock() - Global.canvas.sprite_changed_this_frame = true - - -func blend_colors(color_1 : Color, color_2 : Color) -> Color: - var color := Color() - color.a = color_1.a + color_2.a * (1 - color_1.a) # Blend alpha - if color.a != 0: - # Blend colors - color.r = (color_1.r * color_1.a + color_2.r * color_2.a * (1-color_1.a)) / color.a - color.g = (color_1.g * color_1.a + color_2.g * color_2.a * (1-color_1.a)) / color.a - color.b = (color_1.b * color_1.a + color_2.b * color_2.a * (1-color_1.a)) / color.a - return color - - func scale3X(sprite : Image, tol : float = 50) -> Image: var scaled = Image.new() scaled.create(sprite.get_width()*3, sprite.get_height()*3, false, Image.FORMAT_RGBA8) @@ -829,13 +499,3 @@ func adjust_hsv(img: Image, id : int, delta : float) -> void: img.set_pixel(i,j,c) img.unlock() - - -# Checks if a point is inside a rectangle -func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool: - return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y - - -# Returns the position in the middle of a rectangle -func rectangle_center(rect_position : Vector2, rect_size : Vector2) -> Vector2: - return (rect_position - rect_size / 2).floor() diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index f49380893..65076edb7 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -3,15 +3,8 @@ extends Node enum Grid_Types {CARTESIAN, ISOMETRIC, ALL} enum Pressure_Sensitivity {NONE, ALPHA, SIZE, ALPHA_AND_SIZE} -enum Brush_Types {PIXEL, CIRCLE, FILLED_CIRCLE, FILE, RANDOM_FILE, CUSTOM} enum Direction {UP, DOWN, LEFT, RIGHT} -enum Mouse_Button {LEFT, RIGHT} -enum Tools {PENCIL, ERASER, BUCKET, LIGHTENDARKEN, RECTSELECT, COLORPICKER, ZOOM} enum Theme_Types {DARK, BLUE, CARAMEL, LIGHT} -enum Fill_Area {SAME_COLOR_AREA, SAME_COLOR_PIXELS} -enum Fill_With {COLOR, PATTERN} -enum Lighten_Darken_Mode {LIGHTEN, DARKEN} -enum Zoom_Mode {ZOOM_IN, ZOOM_OUT} # Stuff for arrowkey-based canvas movements nyaa ^.^ const low_speed_move_rate := 150.0 @@ -44,8 +37,8 @@ var pressure_sensitivity_mode = Pressure_Sensitivity.NONE var open_last_project := false var smooth_zoom := true var cursor_image = preload("res://assets/graphics/cursor_icons/cursor.png") -var left_cursor_tool_texture : ImageTexture -var right_cursor_tool_texture : ImageTexture +var left_cursor_tool_texture := ImageTexture.new() +var right_cursor_tool_texture := ImageTexture.new() var image_clipboard : Image var play_only_tags := true @@ -68,27 +61,11 @@ var autosave_interval := 5.0 var enable_autosave := true # Tools & options -var current_tools := [Tools.PENCIL, Tools.ERASER] var show_left_tool_icon := true var show_right_tool_icon := true var left_square_indicator_visible := true var right_square_indicator_visible := false -var fill_areas := [Fill_Area.SAME_COLOR_AREA, Fill_Area.SAME_COLOR_AREA] -var fill_with := [Fill_With.COLOR, Fill_With.COLOR] -var fill_pattern_offsets := [Vector2.ZERO, Vector2.ZERO] - -var ld_modes := [Lighten_Darken_Mode.LIGHTEN, Lighten_Darken_Mode.LIGHTEN] -var ld_amounts := [0.1, 0.1] - -var color_picker_for := [Mouse_Button.LEFT, Mouse_Button.RIGHT] - -var zoom_modes := [Zoom_Mode.ZOOM_IN, Zoom_Mode.ZOOM_OUT] - -var horizontal_mirror := [false, false] -var vertical_mirror := [false, false] -var pixel_perfect := [false, false] - # View menu options var tile_mode := false var draw_grid := false @@ -102,25 +79,6 @@ var onion_skinning_past_rate := 1.0 var onion_skinning_future_rate := 1.0 var onion_skinning_blue_red := false -# Brushes -var file_brushes := [] -var brush_sizes := [1, 1] -var current_brush_types := [Brush_Types.PIXEL, Brush_Types.PIXEL] -var brush_images := [Image.new(), Image.new()] -var brush_textures := [ImageTexture.new(), ImageTexture.new()] - -var brush_type_window_position : int = Mouse_Button.LEFT -var left_circle_points := [] -var right_circle_points := [] - -var brushes_from_files := 0 -var custom_brush_indexes := [-1, -1] - -# Patterns -var patterns := [] -var pattern_window_position : int = Mouse_Button.LEFT -var pattern_images := [Image.new(), Image.new()] - # Palettes var palettes := {} @@ -158,41 +116,11 @@ var export_dialog : AcceptDialog var preferences_dialog : AcceptDialog var unsaved_changes_dialog : ConfirmationDialog -var color_pickers := [] - var color_switch_button : BaseButton -var tool_options_containers := [] - -var brush_type_containers := [] -var brush_type_buttons := [] var brushes_popup : Popup -var file_brush_container : GridContainer -var project_brush_container : GridContainer var patterns_popup : Popup -var brush_size_edits := [] -var brush_size_sliders := [] - -var pixel_perfect_containers := [] - -var color_interpolation_containers := [] -var interpolate_spinboxes := [] -var interpolate_sliders := [] - -var fill_area_containers := [] -var fill_pattern_containers := [] - -var ld_containers := [] -var ld_amount_sliders := [] -var ld_amount_spinboxes := [] - -var colorpicker_containers := [] - -var zoom_containers := [] - -var mirror_containers := [] - var animation_timeline : Panel var animation_timer : Timer @@ -251,21 +179,6 @@ func _ready() -> void: right_cursor = find_node_by_name(root, "RightCursor") canvas = find_node_by_name(root, "Canvas") - var pencil_cursor_image = preload("res://assets/graphics/cursor_icons/pencil_cursor.png") - var eraser_cursor_image = preload("res://assets/graphics/cursor_icons/eraser_cursor.png") - - left_cursor_tool_texture = ImageTexture.new() - if pencil_cursor_image is Image: - left_cursor_tool_texture.create_from_image(pencil_cursor_image) - elif pencil_cursor_image is ImageTexture: - left_cursor_tool_texture.create_from_image(pencil_cursor_image.get_data()) - - right_cursor_tool_texture = ImageTexture.new() - if eraser_cursor_image is Image: - right_cursor_tool_texture.create_from_image(eraser_cursor_image) - elif eraser_cursor_image is ImageTexture: - right_cursor_tool_texture.create_from_image(eraser_cursor_image.get_data()) - tabs = find_node_by_name(root, "Tabs") main_viewport = find_node_by_name(root, "ViewportContainer") second_viewport = find_node_by_name(root, "ViewportContainer2") @@ -294,58 +207,11 @@ func _ready() -> void: preferences_dialog = find_node_by_name(root, "PreferencesDialog") unsaved_changes_dialog = find_node_by_name(root, "UnsavedCanvasDialog") - tool_options_containers.append(find_node_by_name(root, "LeftToolOptions")) - tool_options_containers.append(find_node_by_name(root, "RightToolOptions")) - - color_pickers.append(find_node_by_name(root, "LeftColorPickerButton")) - color_pickers.append(find_node_by_name(root, "RightColorPickerButton")) color_switch_button = find_node_by_name(root, "ColorSwitch") - brush_type_containers.append(find_node_by_name(tool_options_containers[0], "LeftBrushType")) - brush_type_containers.append(find_node_by_name(tool_options_containers[1], "RightBrushType")) - brush_type_buttons.append(find_node_by_name(brush_type_containers[0], "LeftBrushTypeButton")) - brush_type_buttons.append(find_node_by_name(brush_type_containers[1], "RightBrushTypeButton")) brushes_popup = find_node_by_name(root, "BrushesPopup") - file_brush_container = find_node_by_name(brushes_popup, "FileBrushContainer") - project_brush_container = find_node_by_name(brushes_popup, "ProjectBrushContainer") patterns_popup = find_node_by_name(root, "PatternsPopup") - brush_size_edits.append(find_node_by_name(root, "LeftBrushSizeEdit")) - brush_size_sliders.append(find_node_by_name(root, "LeftBrushSizeSlider")) - brush_size_edits.append(find_node_by_name(root, "RightBrushSizeEdit")) - brush_size_sliders.append(find_node_by_name(root, "RightBrushSizeSlider")) - - pixel_perfect_containers.append(find_node_by_name(root, "LeftBrushPixelPerfectMode")) - pixel_perfect_containers.append(find_node_by_name(root, "RightBrushPixelPerfectMode")) - - color_interpolation_containers.append(find_node_by_name(root, "LeftColorInterpolation")) - color_interpolation_containers.append(find_node_by_name(root, "RightColorInterpolation")) - interpolate_spinboxes.append(find_node_by_name(root, "LeftInterpolateFactor")) - interpolate_sliders.append(find_node_by_name(root, "LeftInterpolateSlider")) - interpolate_spinboxes.append(find_node_by_name(root, "RightInterpolateFactor")) - interpolate_sliders.append(find_node_by_name(root, "RightInterpolateSlider")) - - fill_area_containers.append(find_node_by_name(root, "LeftFillArea")) - fill_pattern_containers.append(find_node_by_name(root, "LeftFillPattern")) - fill_area_containers.append(find_node_by_name(root, "RightFillArea")) - fill_pattern_containers.append(find_node_by_name(root, "RightFillPattern")) - - ld_containers.append(find_node_by_name(root, "LeftLDOptions")) - ld_amount_sliders.append(find_node_by_name(root, "LeftLDAmountSlider")) - ld_amount_spinboxes.append(find_node_by_name(root, "LeftLDAmountSpinbox")) - ld_containers.append(find_node_by_name(root, "RightLDOptions")) - ld_amount_sliders.append(find_node_by_name(root, "RightLDAmountSlider")) - ld_amount_spinboxes.append(find_node_by_name(root, "RightLDAmountSpinbox")) - - colorpicker_containers.append(find_node_by_name(root, "LeftColorPickerOptions")) - colorpicker_containers.append(find_node_by_name(root, "RightColorPickerOptions")) - - zoom_containers.append(find_node_by_name(root, "LeftZoomOptions")) - zoom_containers.append(find_node_by_name(root, "RightZoomOptions")) - - mirror_containers.append(find_node_by_name(root, "LeftMirrorButtons")) - mirror_containers.append(find_node_by_name(root, "RightMirrorButtons")) - animation_timeline = find_node_by_name(root, "AnimationTimeline") layers_container = find_node_by_name(animation_timeline, "LayersContainer") @@ -605,143 +471,6 @@ Hold %s to make a line""") % [InputMap.get_action_list("left_eraser_tool")[0].as (%s)""") % InputMap.get_action_list("go_to_last_frame")[0].as_text() -func create_brush_button(brush_img : Image, brush_type := Brush_Types.CUSTOM, hint_tooltip := "") -> void: - var brush_container - var brush_button = load("res://src/UI/BrushButton.tscn").instance() - brush_button.brush_type = brush_type - if brush_type == Brush_Types.FILE || brush_type == Brush_Types.RANDOM_FILE: - brush_button.custom_brush_index = file_brushes.size() - 1 - brush_container = file_brush_container - else: - brush_button.custom_brush_index = current_project.brushes.size() - 1 - brush_container = project_brush_container - var brush_tex := ImageTexture.new() - brush_tex.create_from_image(brush_img, 0) - brush_button.get_child(0).texture = brush_tex - brush_button.hint_tooltip = hint_tooltip - brush_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - if brush_type == Brush_Types.RANDOM_FILE: - brush_button.random_brushes.append(brush_img) - brush_container.add_child(brush_button) - - -func create_pattern_button(image : Image, hint_tooltip := "") -> void: - var pattern_button : BaseButton = load("res://src/UI/PatternButton.tscn").instance() - pattern_button.image = image - var pattern_tex := ImageTexture.new() - pattern_tex.create_from_image(image, 0) - pattern_button.get_child(0).texture = pattern_tex - pattern_button.hint_tooltip = hint_tooltip - pattern_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND - patterns_popup.get_node("ScrollContainer/PatternContainer").add_child(pattern_button) - - -func remove_brush_buttons() -> void: - current_brush_types[0] = Brush_Types.PIXEL - current_brush_types[1] = Brush_Types.PIXEL - for child in project_brush_container.get_children(): - child.queue_free() - - -func undo_custom_brush(_brush_button : BaseButton = null) -> void: - general_undo() - var action_name : String = current_project.undo_redo.get_current_action_name() - if action_name == "Delete Custom Brush": - project_brush_container.add_child(_brush_button) - project_brush_container.move_child(_brush_button, _brush_button.custom_brush_index) - _brush_button.get_node("DeleteButton").visible = false - - -func redo_custom_brush(_brush_button : BaseButton = null) -> void: - general_redo() - var action_name : String = current_project.undo_redo.get_current_action_name() - if action_name == "Delete Custom Brush": - project_brush_container.remove_child(_brush_button) - - -func update_custom_brush(mouse_button : int) -> void: - var brush_type : int = current_brush_types[mouse_button] - if brush_type == Brush_Types.PIXEL: - var pixel = preload("res://assets/graphics/pixel_image.png") - if pixel is Image: - brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0) - elif pixel is ImageTexture: - brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0) - elif brush_type == Brush_Types.CIRCLE: - var pixel = preload("res://assets/graphics/circle_9x9.png") - if pixel is Image: - brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0) - elif pixel is ImageTexture: - brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0) - - left_circle_points = plot_circle(brush_sizes[0]) - right_circle_points = plot_circle(brush_sizes[1]) - elif brush_type == Brush_Types.FILLED_CIRCLE: - var pixel = preload("res://assets/graphics/circle_filled_9x9.png") - if pixel is Image: - brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel, 0) - elif pixel is ImageTexture: - brush_type_buttons[mouse_button].get_child(0).texture.create_from_image(pixel.get_data(), 0) - - left_circle_points = plot_circle(brush_sizes[0]) - right_circle_points = plot_circle(brush_sizes[1]) - else: - var custom_brush := Image.new() - if brush_type == Brush_Types.FILE or brush_type == Brush_Types.RANDOM_FILE: - custom_brush.copy_from(file_brushes[custom_brush_indexes[mouse_button]]) - else: - custom_brush.copy_from(current_project.brushes[custom_brush_indexes[mouse_button]]) - var custom_brush_size = custom_brush.get_size() - custom_brush.resize(custom_brush_size.x * brush_sizes[mouse_button], custom_brush_size.y * brush_sizes[mouse_button], Image.INTERPOLATE_NEAREST) - brush_images[mouse_button] = blend_image_with_color(custom_brush, color_pickers[mouse_button].color, interpolate_spinboxes[mouse_button].value / 100) - brush_textures[mouse_button].create_from_image(brush_images[mouse_button], 0) - - brush_type_buttons[mouse_button].get_child(0).texture = brush_textures[mouse_button] - - -func blend_image_with_color(image : Image, color : Color, interpolate_factor : float) -> Image: - var blended_image := Image.new() - blended_image.copy_from(image) - var size := image.get_size() - blended_image.lock() - for xx in size.x: - for yy in size.y: - if color.a > 0: # If it's the pencil - var current_color := blended_image.get_pixel(xx, yy) - if current_color.a > 0: - var new_color := current_color.linear_interpolate(color, interpolate_factor) - new_color.a = current_color.a - blended_image.set_pixel(xx, yy, new_color) - else: # If color is transparent - if it's the eraser - blended_image.set_pixel(xx, yy, Color(0, 0, 0, 0)) - return blended_image - - -# Algorithm based on http://members.chello.at/easyfilter/bresenham.html -# This is not used for drawing, rather for finding the points required -# for the mouse cursor/position indicator -func plot_circle(r : int) -> Array: - var circle_points := [] - var xm := 0 - var ym := 0 - var x := -r - var y := 0 - var err := 2 - r * 2 - while x < 0: - circle_points.append(Vector2(xm - x, ym + y)) - circle_points.append(Vector2(xm - y, ym - x)) - circle_points.append(Vector2(xm + x, ym - y)) - circle_points.append(Vector2(xm + y, ym + x)) - r = err - if r <= y: - y += 1 - err += y * 2 + 1 - if r > x || err > y: - x += 1 - err += x * 2 + 1 - return circle_points - - func _exit_tree() -> void: config_cache.set_value("window", "screen", OS.current_screen) config_cache.set_value("window", "maximized", OS.window_maximized || OS.window_fullscreen) diff --git a/src/Autoload/Import.gd b/src/Autoload/Import.gd index 94b0c4e42..84938a928 100644 --- a/src/Autoload/Import.gd +++ b/src/Autoload/Import.gd @@ -105,18 +105,7 @@ func add_randomised_brush(fpaths : Array, tooltip_name : String) -> void: if len(loaded_images) > 0: # actually have images # to use. - # take initial image... - var first_image : Image = loaded_images.pop_front() - - # The index which this random brush will be at - var next_random_brush_index : int = Global.file_brush_container.get_child_count() - - Global.file_brushes.append(first_image) - Global.create_brush_button(first_image, Global.Brush_Types.RANDOM_FILE, tooltip_name) - # # Process the rest - for remaining_image in loaded_images: - var brush_button = Global.file_brush_container.get_child(next_random_brush_index) - brush_button.random_brushes.append(remaining_image) + Brushes.add_file_brush(loaded_images, tooltip_name) # Add a plain brush from the given path to the list of brushes. # Taken, again, from find_brushes @@ -127,8 +116,7 @@ func add_plain_brush(path: String, tooltip_name: String) -> void: return # do the standard conversion thing... image.convert(Image.FORMAT_RGBA8) - Global.file_brushes.append(image) - Global.create_brush_button(image, Global.Brush_Types.FILE, tooltip_name) + Brushes.add_file_brush([image], tooltip_name) # Import brushes, in priority order, from the paths in question in priority order @@ -214,8 +202,6 @@ func import_brushes(priority_ordered_search_path: Array) -> void: # Mark this as a processed relpath processed_subdir_paths[nonrandomised_subdir][relative_path] = true - Global.brushes_from_files = Global.file_brushes.size() - func import_patterns(priority_ordered_search_path: Array) -> void: for path in priority_ordered_search_path: @@ -235,25 +221,7 @@ func import_patterns(priority_ordered_search_path: Array) -> void: var err := image.load(path.plus_file(pattern)) if err == OK: image.convert(Image.FORMAT_RGBA8) - Global.patterns.append(image) - Global.create_pattern_button(image, pattern) - - if Global.patterns.size() > 0: - var image_size = Global.patterns[0].get_size() - - Global.pattern_images[0] = Global.patterns[0] - var pattern_left_tex := ImageTexture.new() - pattern_left_tex.create_from_image(Global.pattern_images[0], 0) - Global.fill_pattern_containers[0].get_child(0).get_child(0).texture = pattern_left_tex - Global.fill_pattern_containers[0].get_child(2).get_child(1).max_value = image_size.x - 1 - Global.fill_pattern_containers[0].get_child(3).get_child(1).max_value = image_size.y - 1 - - Global.pattern_images[1] = Global.patterns[0] - var pattern_right_tex := ImageTexture.new() - pattern_right_tex.create_from_image(Global.pattern_images[1], 0) - Global.fill_pattern_containers[1].get_child(0).get_child(0).texture = pattern_right_tex - Global.fill_pattern_containers[1].get_child(2).get_child(1).max_value = image_size.x - 1 - Global.fill_pattern_containers[1].get_child(3).get_child(1).max_value = image_size.y - 1 + Global.patterns_popup.add(image) func import_gpl(path : String, text : String) -> Palette: diff --git a/src/Autoload/OpenSave.gd b/src/Autoload/OpenSave.gd index 4b9ebd5f2..aedac5eb0 100644 --- a/src/Autoload/OpenSave.gd +++ b/src/Autoload/OpenSave.gd @@ -93,7 +93,7 @@ func open_pxo_file(path : String, untitled_backup : bool = false) -> void: var image := Image.new() image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer) new_project.brushes.append(image) - Global.create_brush_button(image) + Brushes.add_project_brush(image) file.close() if !empty_project: @@ -232,19 +232,13 @@ func open_old_pxo_file(file : File, new_project : Project, first_line : String) guide_line = file.get_line() # Load tool options - Global.color_pickers[0].color = file.get_var() - Global.color_pickers[1].color = file.get_var() - Global.brush_sizes[0] = file.get_8() - Global.brush_size_edits[0].value = Global.brush_sizes[0] - Global.brush_sizes[1] = file.get_8() - Global.brush_size_edits[1].value = Global.brush_sizes[1] + file.get_var() + file.get_var() + file.get_8() + file.get_8() if file_major_version == 0 and file_minor_version < 7: - var left_palette = file.get_var() - var right_palette = file.get_var() - for color in left_palette: - Global.color_pickers[0].get_picker().add_preset(color) - for color in right_palette: - Global.color_pickers[1].get_picker().add_preset(color) + file.get_var() + file.get_var() # Load custom brushes var brush_line := file.get_line() @@ -255,7 +249,7 @@ func open_old_pxo_file(file : File, new_project : Project, first_line : String) var image := Image.new() image.create_from_data(b_width, b_height, false, Image.FORMAT_RGBA8, buffer) new_project.brushes.append(image) - Global.create_brush_button(image) + Brushes.add_project_brush(image) brush_line = file.get_line() if file_major_version >= 0 and file_minor_version > 6: diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd new file mode 100644 index 000000000..9e7f379d5 --- /dev/null +++ b/src/Autoload/Tools.gd @@ -0,0 +1,201 @@ +extends Node + + +class Slot: + + var name : String + var kname : String + var tool_node : Node = null + var button : int + var color : Color + + var pixel_perfect := false + var horizontal_mirror := false + var vertical_mirror := false + + + func _init(slot_name : String) -> void: + name = slot_name + kname = name.replace(" ", "_").to_lower() + load_config() + + + func save_config() -> void: + var config := { + "pixel_perfect" : pixel_perfect, + "horizontal_mirror" : horizontal_mirror, + "vertical_mirror" : vertical_mirror, + } + Global.config_cache.set_value(kname, "slot", config) + + + func load_config() -> void: + var config = Global.config_cache.get_value(kname, "slot", {}) + pixel_perfect = config.get("pixel_perfect", pixel_perfect) + horizontal_mirror = config.get("horizontal_mirror", horizontal_mirror) + vertical_mirror = config.get("vertical_mirror", vertical_mirror) + + +signal color_changed(color, button) + +var _tools = { + "RectSelect" : "res://src/Tools/RectSelect.tscn", + "Zoom" : "res://src/Tools/Zoom.tscn", + "ColorPicker" : "res://src/Tools/ColorPicker.tscn", + "Pencil" : "res://src/Tools/Pencil.tscn", + "Eraser" : "res://src/Tools/Eraser.tscn", + "Bucket" : "res://src/Tools/Bucket.tscn", + "LightenDarken" : "res://src/Tools/LightenDarken.tscn", +} +var _slots = {} +var _panels = {} +var _tool_buttons : Node +var _active_button := -1 +var _last_position := Vector2.INF + +var pen_pressure := 1.0 +var control := false +var shift := false +var alt := false + + +func _ready(): + yield(get_tree(), "idle_frame") + _slots[BUTTON_LEFT] = Slot.new("Left tool") + _slots[BUTTON_RIGHT] = Slot.new("Right tool") + _panels[BUTTON_LEFT] = Global.find_node_by_name(Global.control, "LeftPanelContainer") + _panels[BUTTON_RIGHT] = Global.find_node_by_name(Global.control, "RightPanelContainer") + _tool_buttons = Global.find_node_by_name(Global.control, "ToolButtons") + + var value = Global.config_cache.get_value(_slots[BUTTON_LEFT].kname, "tool", "Pencil") + set_tool(value, BUTTON_LEFT) + value = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "tool", "Eraser") + set_tool(value, BUTTON_RIGHT) + value = Global.config_cache.get_value(_slots[BUTTON_LEFT].kname, "color", Color.black) + assign_color(value, BUTTON_LEFT) + value = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "color", Color.white) + assign_color(value, BUTTON_RIGHT) + + update_tool_buttons() + update_tool_cursors() + + +func set_tool(name : String, button : int) -> void: + var slot = _slots[button] + var panel : Node = _panels[button] + var node : Node = load(_tools[name]).instance() + node.name = name + node.tool_slot = slot + slot.tool_node = node + slot.button = button + panel.add_child(slot.tool_node) + + +func assign_tool(name : String, button : int) -> void: + var slot = _slots[button] + var panel : Node = _panels[button] + + if slot.tool_node != null: + if slot.tool_node.name == name: + return + panel.remove_child(slot.tool_node) + slot.tool_node.queue_free() + + set_tool(name, button) + update_tool_buttons() + update_tool_cursors() + Global.config_cache.set_value(slot.kname, "tool", name) + + +func default_color() -> void: + assign_color(Color.black, BUTTON_LEFT) + assign_color(Color.white, BUTTON_RIGHT) + + +func swap_color() -> void: + var left = _slots[BUTTON_LEFT].color + var right = _slots[BUTTON_RIGHT].color + assign_color(right, BUTTON_LEFT) + assign_color(left, BUTTON_RIGHT) + + +func assign_color(color : Color, button : int) -> void: + var c : Color = _slots[button].color + if color.a == 0: + if color.r != c.r or color.g != c.g or color.b != c.b: + color.a = 1 + _slots[button].color = color + Global.config_cache.set_value(_slots[button].kname, "color", color) + emit_signal("color_changed", color, button) + + +func get_assigned_color(button : int) -> Color: + return _slots[button].color + + +func update_tool_buttons() -> void: + for child in _tool_buttons.get_children(): + var texture : TextureRect = child.get_child(0) + var filename = child.name.to_lower() + if _slots[BUTTON_LEFT].tool_node.name == child.name: + filename += "_l" + if _slots[BUTTON_RIGHT].tool_node.name == child.name: + filename += "_r" + filename += ".png" + Global.change_button_texturerect(texture, filename) + + +func update_tool_cursors() -> void: + var image = "res://assets/graphics/cursor_icons/%s_cursor.png" % _slots[BUTTON_LEFT].tool_node.name.to_lower() + Global.left_cursor_tool_texture.create_from_image(load(image), 0) + image = "res://assets/graphics/cursor_icons/%s_cursor.png" % _slots[BUTTON_RIGHT].tool_node.name.to_lower() + Global.right_cursor_tool_texture.create_from_image(load(image), 0) + + +func draw_indicator() -> void: + if Global.left_square_indicator_visible: + _slots[BUTTON_LEFT].tool_node.draw_indicator() + if Global.right_square_indicator_visible: + _slots[BUTTON_RIGHT].tool_node.draw_indicator() + + +func handle_draw(position : Vector2, event : InputEvent) -> void: + if not (Global.can_draw and Global.has_focus): + return + + if event is InputEventWithModifiers: + control = event.control + shift = event.shift + alt = event.alt + + if event is InputEventMouseButton: + if event.button_index in [BUTTON_LEFT, BUTTON_RIGHT]: + if event.pressed and _active_button == -1: + _active_button = event.button_index + _slots[_active_button].tool_node.draw_start(position) + elif not event.pressed and event.button_index == _active_button: + _slots[_active_button].tool_node.draw_end(position) + _active_button = -1 + + if event is InputEventMouseMotion: + if Engine.get_version_info().major == 3 && Engine.get_version_info().minor >= 2: + pen_pressure = event.pressure + if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.NONE: + pen_pressure = 1.0 + + if not position.is_equal_approx(_last_position): + _last_position = position + _slots[BUTTON_LEFT].tool_node.cursor_move(position) + _slots[BUTTON_RIGHT].tool_node.cursor_move(position) + if _active_button != -1: + _slots[_active_button].tool_node.draw_move(position) + + var project : Project = Global.current_project + var text := "[%s×%s]" % [project.size.x, project.size.y] + if Global.has_focus: + text += " %s, %s" % [position.x, position.y] + if not _slots[BUTTON_LEFT].tool_node.cursor_text.empty(): + text += " %s" % _slots[BUTTON_LEFT].tool_node.cursor_text + if not _slots[BUTTON_RIGHT].tool_node.cursor_text.empty(): + text += " %s" % _slots[BUTTON_RIGHT].tool_node.cursor_text + Global.cursor_position_label.text = text diff --git a/src/Canvas.gd b/src/Canvas.gd index 0fade434a..94d9127dd 100644 --- a/src/Canvas.gd +++ b/src/Canvas.gd @@ -5,17 +5,9 @@ extends Node2D var location := Vector2.ZERO var fill_color := Color(0, 0, 0, 0) var current_pixel := Vector2.ZERO # pretty much same as mouse_pos, but can be accessed externally -var previous_mouse_pos := Vector2.ZERO -var previous_mouse_pos_for_lines := Vector2.ZERO var can_undo := true var cursor_image_has_changed := false -var previous_action := -1 var sprite_changed_this_frame := false # for optimization purposes -var is_making_line := false -var made_line := false -var is_making_selection := -1 -var line_pos = [] -var pen_pressure := 1.0 # For tablet pressure sensitivity # Called when the node enters the scene tree for the first time. @@ -23,7 +15,6 @@ func _ready() -> void: var frame : Frame = new_empty_frame(true) Global.current_project.frames.append(frame) camera_zoom() - line_pos = [previous_mouse_pos_for_lines, previous_mouse_pos_for_lines] func _draw() -> void: @@ -54,111 +45,40 @@ func _draw() -> void: draw_grid(Global.grid_type) # Draw rectangle to indicate the pixel currently being hovered on - if Global.can_draw: - var mouse_pos := current_pixel - mouse_pos = mouse_pos.floor() - var visible_indicators := [Global.left_square_indicator_visible, Global.right_square_indicator_visible] - - for i in range(0, 1): - if visible_indicators[i]: - if Global.current_brush_types[i] == Global.Brush_Types.PIXEL || Global.current_tools[i] == Global.Tools.LIGHTENDARKEN: - if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER || Global.current_tools[i] == Global.Tools.LIGHTENDARKEN: - var start_pos_x = mouse_pos.x - (Global.brush_sizes[i] >> 1) - var start_pos_y = mouse_pos.y - (Global.brush_sizes[i] >> 1) - draw_rect(Rect2(start_pos_x, start_pos_y, Global.brush_sizes[i], Global.brush_sizes[i]), Color.blue, false) - #Check for tile mode - if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)): - if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)): - var new_start_pos_x = posmod(start_pos_x,Global.current_project.size.x) - var new_start_pos_y = posmod(start_pos_y,Global.current_project.size.y) - draw_rect(Rect2(new_start_pos_x, new_start_pos_y, Global.brush_sizes[i], Global.brush_sizes[i]), Color.green, false) - if is_making_line: - var line_rect = plot_line(line_pos[1], line_pos[0]) - for rect in line_rect: - draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false) - #Check for tile mode - if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)): - if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)): - rect.x = posmod(rect.x,Global.current_project.size.x) - rect.y = posmod(rect.y,Global.current_project.size.y) - draw_rect(Rect2(rect, Vector2.ONE), Color.green, false) - - elif Global.current_brush_types[i] == Global.Brush_Types.CIRCLE || Global.current_brush_types[i] == Global.Brush_Types.FILLED_CIRCLE: - if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER: - draw_set_transform(mouse_pos, rotation, scale) - for rect in Global.left_circle_points: - draw_rect(Rect2(rect, Vector2.ONE), Color.blue, false) - - #Check for tile mode - if Global.tile_mode and point_in_rectangle(mouse_pos,Vector2( - Global.current_project.size.x - 1 , - Global.current_project.size.y -1 ), Vector2(2 * Global.current_project.size.x, 2 * Global.current_project.size.y)): - if !point_in_rectangle(mouse_pos, Vector2(Global.current_project.x_min - 1,Global.current_project.y_min - 1), Vector2(Global.current_project.x_max,Global.current_project.y_max)): - var pos = mouse_pos.posmodv(Global.current_project.size) - if pos != mouse_pos: - draw_set_transform(pos,rotation,scale) - for rect in Global.left_circle_points: - draw_rect(Rect2(rect, Vector2.ONE), Color.green, false) - draw_set_transform(position, rotation, scale) - else: - if Global.current_tools[i] == Global.Tools.PENCIL || Global.current_tools[i] == Global.Tools.ERASER: - var custom_brush_size = Global.brush_images[i].get_size() - Vector2.ONE - var dst : Vector2 = DrawingAlgos.rectangle_center(mouse_pos, custom_brush_size) - draw_texture(Global.brush_textures[i], dst) + if Global.has_focus and Global.can_draw: + Tools.draw_indicator() func _input(event : InputEvent) -> void: # Don't process anything below if the input isn't a mouse event, or Shift/Ctrl. # This decreases CPU/GPU usage slightly. if not event is InputEventMouse: - if event is InputEventKey: - if event.scancode != KEY_SHIFT && event.scancode != KEY_CONTROL: - return - else: + if not event is InputEventKey: return - - if (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")): - made_line = false - DrawingAlgos.reset() - can_undo = true + elif not event.scancode in [KEY_SHIFT, KEY_CONTROL]: + return +# elif not get_viewport_rect().has_point(event.position): +# return current_pixel = get_local_mouse_position() + location if Global.has_focus: update() - # Godot 3.2 and above only code - if Engine.get_version_info().major == 3 && Engine.get_version_info().minor >= 2: - if event is InputEventMouseMotion: - if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.NONE: - pen_pressure = 1 - else: - pen_pressure = event.pressure - sprite_changed_this_frame = false - var mouse_pos := current_pixel - var mouse_pos_floored := mouse_pos.floor() - var current_mouse_button := -1 - var current_project : Project = Global.current_project + var current_project : Project = Global.current_project current_project.x_min = location.x current_project.x_max = location.x + current_project.size.x current_project.y_min = location.y current_project.y_max = location.y + current_project.size.y - if current_project.selected_pixels.size() != 0: - current_project.x_min = max(current_project.x_min, Global.selection_rectangle.polygon[0].x) - current_project.x_max = min(current_project.x_max, Global.selection_rectangle.polygon[2].x) - current_project.y_min = max(current_project.y_min, Global.selection_rectangle.polygon[0].y) - current_project.y_max = min(current_project.y_max, Global.selection_rectangle.polygon[2].y) - - if Input.is_mouse_button_pressed(BUTTON_LEFT): - current_mouse_button = Global.Mouse_Button.LEFT - - elif Input.is_mouse_button_pressed(BUTTON_RIGHT): - current_mouse_button = Global.Mouse_Button.RIGHT - - var current_action : int = Global.current_tools[current_mouse_button] if current_mouse_button != -1 else -1 + if not current_project.selected_rect.has_no_area(): + current_project.x_min = max(current_project.x_min, current_project.selected_rect.position.x) + current_project.x_max = min(current_project.x_max, current_project.selected_rect.end.x) + current_project.y_min = max(current_project.y_min, current_project.selected_rect.position.y) + current_project.y_max = min(current_project.y_max, current_project.selected_rect.end.y) if Global.has_focus: - Global.cursor_position_label.text = "[%s×%s] %s, %s" % [current_project.size.x, current_project.size.y, mouse_pos_floored.x, mouse_pos_floored.y] if !cursor_image_has_changed: cursor_image_has_changed = true if Global.show_left_tool_icon: @@ -166,80 +86,13 @@ func _input(event : InputEvent) -> void: if Global.show_right_tool_icon: Global.right_cursor.visible = true else: - Global.cursor_position_label.text = "[%s×%s]" % [current_project.size.x, current_project.size.y] if cursor_image_has_changed: cursor_image_has_changed = false Global.left_cursor.visible = false Global.right_cursor.visible = false - # Handle Undo/Redo - var can_handle : bool = Global.can_draw && Global.has_focus && !made_line - var mouse_pressed : bool = (Input.is_action_just_pressed("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_pressed("right_mouse") && !Input.is_action_pressed("left_mouse")) + Tools.handle_draw(current_pixel.floor(), event) - if mouse_pressed: - if can_handle || is_making_line: - if current_action != -1 && current_action != Global.Tools.COLORPICKER && current_action != Global.Tools.ZOOM: - if current_action == Global.Tools.RECTSELECT: - handle_undo("Rectangle Select") - else: - handle_undo("Draw") - elif (Input.is_action_just_released("left_mouse") && !Input.is_action_pressed("right_mouse")) || (Input.is_action_just_released("right_mouse") && !Input.is_action_pressed("left_mouse")): - if can_handle || current_project.undos == current_project.undo_redo.get_version(): - if previous_action != -1 && previous_action != Global.Tools.RECTSELECT && current_action != Global.Tools.COLORPICKER && current_action != Global.Tools.ZOOM: - handle_redo("Draw") - - handle_tools(current_mouse_button, current_action, mouse_pos, can_handle) - - if Global.can_draw && Global.has_focus && Input.is_action_just_pressed("shift") && ([Global.Tools.PENCIL, Global.Tools.ERASER, Global.Tools.LIGHTENDARKEN].has(Global.current_tools[0]) || [Global.Tools.PENCIL, Global.Tools.ERASER, Global.Tools.LIGHTENDARKEN].has(Global.current_tools[1])): - is_making_line = true - line_pos[0] = previous_mouse_pos_for_lines - elif Input.is_action_just_released("shift"): - is_making_line = false - line_pos[1] = line_pos[0] - - if is_making_line: - var point0 : Vector2 = line_pos[0] - var angle := stepify(rad2deg(mouse_pos.angle_to_point(point0)), 0.01) - if Input.is_action_pressed("ctrl"): - angle = round(angle / 15) * 15 - var distance : float = point0.distance_to(mouse_pos) - line_pos[1] = point0 + Vector2.RIGHT.rotated(deg2rad(angle)) * distance - else: - line_pos[1] = mouse_pos - - if angle < 0: - angle = 360 + angle - Global.cursor_position_label.text += " %s°" % str(angle) - - if is_making_selection != -1: # If we're making a selection - var mouse_button_string := "left_mouse" if is_making_selection == Global.Mouse_Button.LEFT else "right_mouse" - - if Input.is_action_just_released(mouse_button_string): # Finish selection when button is released - var start_pos = Global.selection_rectangle.polygon[0] - var end_pos = Global.selection_rectangle.polygon[2] - if start_pos.x > end_pos.x: - var temp = end_pos.x - end_pos.x = start_pos.x - start_pos.x = temp - - if start_pos.y > end_pos.y: - var temp = end_pos.y - end_pos.y = start_pos.y - start_pos.y = temp - - Global.selection_rectangle.polygon[0] = start_pos - Global.selection_rectangle.polygon[1] = Vector2(end_pos.x, start_pos.y) - Global.selection_rectangle.polygon[2] = end_pos - Global.selection_rectangle.polygon[3] = Vector2(start_pos.x, end_pos.y) - - for xx in range(start_pos.x, end_pos.x): - for yy in range(start_pos.y, end_pos.y): - current_project.selected_pixels.append(Vector2(xx, yy)) - is_making_selection = -1 - handle_redo("Rectangle Select") - - previous_action = current_action - previous_mouse_pos = current_pixel if sprite_changed_this_frame: update_texture(current_project.current_layer) @@ -284,143 +137,6 @@ func new_empty_frame(first_time := false, single_layer := false, size := Global. return frame -func handle_tools(current_mouse_button : int, current_action : int, mouse_pos : Vector2, can_handle : bool) -> void: - var current_project : Project = Global.current_project - var current_cel : Cel = current_project.frames[current_project.current_frame].cels[current_project.current_layer] - var sprite : Image = current_cel.image - var mouse_pos_floored := mouse_pos.floor() - var mouse_pos_ceiled := mouse_pos.ceil() - - var current_color : Color = Global.color_pickers[current_mouse_button].color - var fill_area : int = Global.fill_areas[current_mouse_button] - var ld : int = Global.ld_modes[current_mouse_button] - var ld_amount : float = Global.ld_amounts[current_mouse_button] - var color_picker_for : int = Global.color_picker_for[current_mouse_button] - var zoom_mode : int = Global.zoom_modes[current_mouse_button] - - match current_action: # Handle current tool - Global.Tools.PENCIL: - pencil_and_eraser(sprite, mouse_pos, current_color, current_mouse_button, current_action) - Global.Tools.ERASER: - pencil_and_eraser(sprite, mouse_pos, Color(0, 0, 0, 0), current_mouse_button, current_action) - Global.Tools.BUCKET: - if can_handle: - var fill_with : int = Global.fill_with[current_mouse_button] - var pattern_image : Image = Global.pattern_images[current_mouse_button] - var pattern_offset : Vector2 = Global.fill_pattern_offsets[current_mouse_button] - - if fill_area == Global.Fill_Area.SAME_COLOR_AREA: # Paint the specific area of the same color - var mirror_x := current_project.x_max + current_project.x_min - mouse_pos_floored.x - 1 - var mirror_y := current_project.y_max + current_project.y_min - mouse_pos_floored.y - 1 - var horizontal_mirror : bool = Global.horizontal_mirror[current_mouse_button] - var vertical_mirror : bool = Global.vertical_mirror[current_mouse_button] - - if fill_with == Global.Fill_With.PATTERN && pattern_image: # Pattern fill - DrawingAlgos.pattern_fill(sprite, mouse_pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) - if horizontal_mirror: - var pos := Vector2(mirror_x, mouse_pos.y) - DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) - if vertical_mirror: - var pos := Vector2(mouse_pos.x, mirror_y) - DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) - if horizontal_mirror && vertical_mirror: - var pos := Vector2(mirror_x, mirror_y) - DrawingAlgos.pattern_fill(sprite, pos, pattern_image, sprite.get_pixelv(mouse_pos), pattern_offset) - - else: # Flood fill - DrawingAlgos.flood_fill(sprite, mouse_pos, sprite.get_pixelv(mouse_pos), current_color) - if horizontal_mirror: - var pos := Vector2(mirror_x, mouse_pos.y) - DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) - if vertical_mirror: - var pos := Vector2(mouse_pos.x, mirror_y) - DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) - if horizontal_mirror && vertical_mirror: - var pos := Vector2(mirror_x, mirror_y) - DrawingAlgos.flood_fill(sprite, pos, sprite.get_pixelv(pos), current_color) - - else: # Paint all pixels of the same color - var pixel_color : Color = sprite.get_pixelv(mouse_pos) - for xx in range(current_project.x_min, current_project.x_max): - for yy in range(current_project.y_min, current_project.y_max): - var c : Color = sprite.get_pixel(xx, yy) - if c == pixel_color: - if fill_with == Global.Fill_With.PATTERN && pattern_image: # Pattern fill - pattern_image.lock() - var pattern_size := pattern_image.get_size() - var xxx : int = int(xx + pattern_offset.x) % int(pattern_size.x) - var yyy : int = int(yy + pattern_offset.y) % int(pattern_size.y) - var pattern_color : Color = pattern_image.get_pixel(xxx, yyy) - sprite.set_pixel(xx, yy, pattern_color) - pattern_image.unlock() - else: - sprite.set_pixel(xx, yy, current_color) - sprite_changed_this_frame = true - Global.Tools.LIGHTENDARKEN: - if can_handle: - var pixel_color : Color = sprite.get_pixelv(mouse_pos) - var color_changed : Color - if ld == Global.Lighten_Darken_Mode.LIGHTEN: - color_changed = pixel_color.lightened(ld_amount) - else: # Darken - color_changed = pixel_color.darkened(ld_amount) - pencil_and_eraser(sprite, mouse_pos, color_changed, current_mouse_button, current_action) - Global.Tools.RECTSELECT: - # Check SelectionRectangle.gd for more code on Rectangle Selection - if Global.can_draw && Global.has_focus: - # If we're creating a new selection - if current_project.selected_pixels.size() == 0 || !point_in_rectangle(mouse_pos_floored, Global.selection_rectangle.polygon[0] - Vector2.ONE, Global.selection_rectangle.polygon[2]): - var mouse_button_string := "left_mouse" if current_mouse_button == Global.Mouse_Button.LEFT else "right_mouse" - - if Input.is_action_just_pressed(mouse_button_string): - Global.selection_rectangle.polygon[0] = mouse_pos_floored - Global.selection_rectangle.polygon[1] = mouse_pos_floored - Global.selection_rectangle.polygon[2] = mouse_pos_floored - Global.selection_rectangle.polygon[3] = mouse_pos_floored - is_making_selection = current_mouse_button - current_project.selected_pixels.clear() - else: - if is_making_selection != -1: # If we're making a new selection... - var start_pos = Global.selection_rectangle.polygon[0] - if start_pos != mouse_pos_floored: - var end_pos := Vector2(mouse_pos_ceiled.x, mouse_pos_ceiled.y) - if mouse_pos.x < start_pos.x: - end_pos.x = mouse_pos_ceiled.x - 1 - if mouse_pos.y < start_pos.y: - end_pos.y = mouse_pos_ceiled.y - 1 - Global.selection_rectangle.polygon[1] = Vector2(end_pos.x, start_pos.y) - Global.selection_rectangle.polygon[2] = end_pos - Global.selection_rectangle.polygon[3] = Vector2(start_pos.x, end_pos.y) - Global.Tools.COLORPICKER: - var canvas_rect := Rect2(location, current_project.size) - if can_handle && canvas_rect.has_point(mouse_pos): - var image_data := Image.new() - image_data.copy_from(sprite) - image_data.lock() - var pixel_color : Color = image_data.get_pixelv(mouse_pos) - Global.color_pickers[color_picker_for].color = pixel_color - Global.update_custom_brush(color_picker_for) - Global.Tools.ZOOM: - if can_handle: - if zoom_mode == Global.Zoom_Mode.ZOOM_IN: - Global.camera.zoom_camera(-1) - else: - Global.camera.zoom_camera(1) - - -func pencil_and_eraser(sprite : Image, mouse_pos : Vector2, color : Color, current_mouse_button : int, current_action := -1) -> void: - if made_line: - return - if is_making_line: - DrawingAlgos.fill_gaps(sprite, line_pos[1], previous_mouse_pos_for_lines, color, current_mouse_button, pen_pressure, current_action) - DrawingAlgos.draw_brush(sprite, line_pos[1], color, current_mouse_button, pen_pressure, current_action) - made_line = true - else: - # Draw - DrawingAlgos.draw_brush(sprite, mouse_pos, color, current_mouse_button, pen_pressure, current_action) - DrawingAlgos.fill_gaps(sprite, mouse_pos, previous_mouse_pos, color, current_mouse_button, pen_pressure, current_action) # Fill the gaps - - func handle_undo(action : String) -> void: if !can_undo: return @@ -442,16 +158,12 @@ func handle_undo(action : String) -> void: var data = f.cels[Global.current_project.current_layer].image.data f.cels[Global.current_project.current_layer].image.lock() Global.current_project.undo_redo.add_undo_property(f.cels[Global.current_project.current_layer].image, "data", data) - if action == "Rectangle Select": - var selected_pixels = Global.current_project.selected_pixels.duplicate() - Global.current_project.undo_redo.add_undo_property(Global.selection_rectangle, "polygon", Global.selection_rectangle.polygon) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "selected_pixels", selected_pixels) Global.current_project.undo_redo.add_undo_method(Global, "undo", frame_index, layer_index) can_undo = false -func handle_redo(action : String) -> void: +func handle_redo(_action : String) -> void: can_undo = true if Global.current_project.undos < Global.current_project.undo_redo.get_version(): @@ -467,9 +179,6 @@ func handle_redo(action : String) -> void: frames = Global.current_project.frames for f in frames: Global.current_project.undo_redo.add_do_property(f.cels[Global.current_project.current_layer].image, "data", f.cels[Global.current_project.current_layer].image.data) - if action == "Rectangle Select": - Global.current_project.undo_redo.add_do_property(Global.selection_rectangle, "polygon", Global.selection_rectangle.polygon) - Global.current_project.undo_redo.add_do_property(Global.current_project, "selected_pixels", Global.current_project.selected_pixels) Global.current_project.undo_redo.add_do_method(Global, "redo", frame_index, layer_index) Global.current_project.undo_redo.commit_action() @@ -561,34 +270,3 @@ func draw_grid(grid_type : int) -> void: for x in range(0, size.x, Global.grid_height * 2): var yy2 = (size.x - x) * tan(deg2rad(26.565)) # 30 degrees draw_line(Vector2(x, size.y), Vector2(size.x, size.y - yy2), Global.grid_color) - - -# Bresenham's Algorithm -# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency -func plot_line(end_pos : Vector2, start_pos : Vector2) -> Array: - start_pos = start_pos.floor() - end_pos = end_pos.floor() - var line_points := [] - var dx := int(abs(end_pos.x - start_pos.x)) - var dy := int(-abs(end_pos.y - start_pos.y)) - var err := dx + dy - var e2 := err << 1 # err * 2 - var sx = 1 if start_pos.x < end_pos.x else -1 - var sy = 1 if start_pos.y < end_pos.y else -1 - var x = start_pos.x - var y = start_pos.y - while !(x == end_pos.x && y == end_pos.y): - line_points.append(Vector2(x, y)) - e2 = err << 1 - if e2 >= dy: - err += dy - x += sx - if e2 <= dx: - err += dx - y += sy - return line_points - - -# Checks if a point is inside a rectangle -func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool: - return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y diff --git a/src/Classes/Drawers.gd b/src/Classes/Drawers.gd index 753d47fea..40994ed0d 100644 --- a/src/Classes/Drawers.gd +++ b/src/Classes/Drawers.gd @@ -1,8 +1,20 @@ class_name Drawer + +class ColorOp: + var strength := 1.0 + + + func process(src: Color, _dst: Color) -> Color: + return src + + class SimpleDrawer: - func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void: - _sprite.set_pixel(_pos.x, _pos.y, _new_color) + func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void: + var color_old := image.get_pixelv(position) + var color_new := op.process(color, color_old) + if not color_new.is_equal_approx(color_old): + image.set_pixelv(position, color_new) class PixelPerfectDrawer: @@ -15,9 +27,10 @@ class PixelPerfectDrawer: last_pixels = [null, null] - func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void: - last_pixels.push_back([_pos, _sprite.get_pixel(_pos.x, _pos.y)]) - _sprite.set_pixel(_pos.x, _pos.y, _new_color) + func set_pixel(image: Image, position: Vector2, color: Color, op : ColorOp) -> void: + var color_old = image.get_pixelv(position) + last_pixels.push_back([position, color_old]) + image.set_pixelv(position, op.process(color, color_old)) var corner = last_pixels.pop_front() var neighbour = last_pixels[0] @@ -25,14 +38,15 @@ class PixelPerfectDrawer: if corner == null or neighbour == null: return - if _pos - corner[0] in corners and _pos - neighbour[0] in neighbours: - _sprite.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1]) + if position - corner[0] in corners and position - neighbour[0] in neighbours: + image.set_pixel(neighbour[0].x, neighbour[0].y, neighbour[1]) last_pixels[0] = corner var pixel_perfect := false setget set_pixel_perfect -var h_mirror := false -var v_mirror := false +var horizontal_mirror := false +var vertical_mirror := false +var color_op := ColorOp.new() var simple_drawer := SimpleDrawer.new() var pixel_perfect_drawers = [PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new(), PixelPerfectDrawer.new()] @@ -52,14 +66,14 @@ func set_pixel_perfect(value: bool) -> void: drawers = [simple_drawer, simple_drawer, simple_drawer, simple_drawer] -func set_pixel(_sprite: Image, _pos: Vector2, _new_color: Color) -> void: - var mirror_x = Global.current_project.x_max + Global.current_project.x_min - _pos.x - 1 - var mirror_y = Global.current_project.y_max + Global.current_project.y_min - _pos.y - 1 +func set_pixel(image: Image, position: Vector2, color: Color) -> void: + var mirror_x = Global.current_project.x_max + Global.current_project.x_min - position.x - 1 + var mirror_y = Global.current_project.y_max + Global.current_project.y_min - position.y - 1 - drawers[0].set_pixel(_sprite, _pos, _new_color) - if h_mirror: - drawers[1].set_pixel(_sprite, Vector2(mirror_x, _pos.y), _new_color) - if v_mirror: - drawers[2].set_pixel(_sprite, Vector2(mirror_x, mirror_y), _new_color) - if v_mirror: - drawers[3].set_pixel(_sprite, Vector2(_pos.x, mirror_y), _new_color) + drawers[0].set_pixel(image, position, color, color_op) + if horizontal_mirror: + drawers[1].set_pixel(image, Vector2(mirror_x, position.y), color, color_op) + if vertical_mirror: + drawers[2].set_pixel(image, Vector2(mirror_x, mirror_y), color, color_op) + if vertical_mirror: + drawers[3].set_pixel(image, Vector2(position.x, mirror_y), color, color_op) diff --git a/src/Classes/Project.gd b/src/Classes/Project.gd index f0db88baf..71d6744f4 100644 --- a/src/Classes/Project.gd +++ b/src/Classes/Project.gd @@ -16,12 +16,13 @@ var guides := [] # Array of Guides var brushes := [] # Array of Images -var selected_pixels := [] var x_min := 0 var x_max := 64 var y_min := 0 var y_max := 64 +var selected_rect := Rect2(0, 0, 0, 0) setget _set_selected_rect + # 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 var cameras_offset := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO] # Array of Vector2 @@ -39,6 +40,11 @@ func _init(_frames := [], _name := tr("untitled")) -> void: OpenSave.backup_save_paths.append("") +func _set_selected_rect(value : Rect2) -> void: + selected_rect = value + Global.selection_rectangle.set_rect(value) + + func change_project() -> void: # Remove old nodes for container in Global.layers_container.get_children(): @@ -94,16 +100,7 @@ func change_project() -> void: self.animation_tags = animation_tags # Change the selection rectangle - if selected_pixels.size() != 0: - Global.selection_rectangle.polygon[0] = Vector2(x_min, y_min) - Global.selection_rectangle.polygon[1] = Vector2(x_max, y_min) - Global.selection_rectangle.polygon[2] = Vector2(x_max, y_max) - Global.selection_rectangle.polygon[3] = Vector2(x_min, y_max) - else: - Global.selection_rectangle.polygon[0] = Vector2.ZERO - Global.selection_rectangle.polygon[1] = Vector2.ZERO - Global.selection_rectangle.polygon[2] = Vector2.ZERO - Global.selection_rectangle.polygon[3] = Vector2.ZERO + Global.selection_rectangle.set_rect(selected_rect) # Change the guides for guide in Global.canvas.get_children(): @@ -114,11 +111,9 @@ func change_project() -> void: guide.visible = false # Change the project brushes - for child in Global.project_brush_container.get_children(): - child.queue_free() - + Brushes.clear_project_brush() for brush in brushes: - Global.create_brush_button(brush) + Brushes.add_project_brush(brush) var cameras = [Global.camera, Global.camera2, Global.camera_preview] var i := 0 diff --git a/src/Main.gd b/src/Main.gd index 4c2ea147f..2b8db3306 100644 --- a/src/Main.gd +++ b/src/Main.gd @@ -19,9 +19,6 @@ func _ready() -> void: Import.import_brushes(Global.directory_module.get_brushes_search_path_in_order()) Import.import_patterns(Global.directory_module.get_patterns_search_path_in_order()) - Global.color_pickers[0].get_picker().presets_visible = false - Global.color_pickers[1].get_picker().presets_visible = false - $QuitAndSaveDialog.add_button("Save & Exit", false, "Save") $QuitAndSaveDialog.get_ok().text = "Exit without saving" diff --git a/src/Palette/EditPalettePopup.gd b/src/Palette/EditPalettePopup.gd index 8f413f1ed..65177e559 100644 --- a/src/Palette/EditPalettePopup.gd +++ b/src/Palette/EditPalettePopup.gd @@ -27,8 +27,8 @@ func open(palette : String) -> void: self.popup_centered() Global.dialog_open(true) - left_color_button.modulate = Global.color_pickers[0].color - right_color_button.modulate = Global.color_pickers[1].color + left_color_button.modulate = Tools.get_assigned_color(BUTTON_LEFT) + right_color_button.modulate = Tools.get_assigned_color(BUTTON_RIGHT) func _display_palette() -> void: @@ -177,12 +177,12 @@ func _refresh_hint_tooltip(_index : int) -> void: func _on_LeftColor_pressed() -> void: - color_picker.color = Global.color_pickers[0].color + color_picker.color = Tools.get_assigned_color(BUTTON_LEFT) _on_EditPaletteColorPicker_color_changed(color_picker.color) func _on_RightColor_pressed() -> void: - color_picker.color = Global.color_pickers[1].color + color_picker.color = Tools.get_assigned_color(BUTTON_RIGHT) _on_EditPaletteColorPicker_color_changed(color_picker.color) diff --git a/src/Palette/PaletteContainer.gd b/src/Palette/PaletteContainer.gd index 4a98de4bb..932536521 100644 --- a/src/Palette/PaletteContainer.gd +++ b/src/Palette/PaletteContainer.gd @@ -220,11 +220,9 @@ func on_color_select(index : int) -> void: var color : Color = Global.palettes[current_palette].get_color(index) if Input.is_action_just_pressed("left_mouse"): - Global.color_pickers[0].color = color - Global.update_custom_brush(0) + Tools.assign_color(color, BUTTON_LEFT) elif Input.is_action_just_pressed("right_mouse"): - Global.color_pickers[1].color = color - Global.update_custom_brush(1) + Tools.assign_color(color, BUTTON_RIGHT) func _load_palettes() -> void: diff --git a/src/SelectionRectangle.gd b/src/SelectionRectangle.gd index b3acfdd90..7e82e793e 100644 --- a/src/SelectionRectangle.gd +++ b/src/SelectionRectangle.gd @@ -1,144 +1,156 @@ extends Polygon2D -var img : Image -var tex : ImageTexture -var is_dragging := false -var move_pixels := false -var diff_x := 0.0 -var diff_y := 0.0 -var orig_x := 0.0 -var orig_y := 0.0 -var orig_colors := [] + +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: - img = Image.new() - img.create(1, 1, false, Image.FORMAT_RGBA8) - img.lock() - tex = ImageTexture.new() - tex.create_from_image(img, 0) - - -func _process(_delta : float) -> void: - if Global.current_project.layers[Global.current_project.current_layer].locked: - return - var mouse_pos: Vector2 = get_local_mouse_position() - Global.canvas.location - var mouse_pos_floored := mouse_pos.floor() - var start_pos := polygon[0] - var end_pos := polygon[2] - var current_layer_index : int = Global.current_project.current_layer - var layer : Image = Global.current_project.frames[Global.current_project.current_frame].cels[current_layer_index].image - - if end_pos == start_pos: - visible = false - else: - visible = true - - if Global.can_draw and Global.has_focus and point_in_rectangle(mouse_pos, polygon[0], polygon[2]) and Global.current_project.selected_pixels.size() > 0 and (Global.current_tools[0] == Global.Tools.RECTSELECT or Global.current_tools[1] == Global.Tools.RECTSELECT): - get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_MOVE - Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE) - if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_just_pressed("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_just_pressed("right_mouse")): - # Begin dragging - is_dragging = true - if Input.is_key_pressed(KEY_SHIFT): - move_pixels = true - else: - move_pixels = false - img.fill(Color(0, 0, 0, 0)) - diff_x = end_pos.x - mouse_pos_floored.x - diff_y = end_pos.y - mouse_pos_floored.y - orig_x = start_pos.x - mouse_pos_floored.x - orig_y = start_pos.y - mouse_pos_floored.y - if move_pixels: - img.unlock() - img.resize(polygon[2].x - polygon[0].x, polygon[2].y - polygon[0].y, 0) - img.lock() - for i in range(Global.current_project.selected_pixels.size()): - var curr_px = Global.current_project.selected_pixels[i] - if point_in_rectangle(curr_px, Global.canvas.location - Vector2.ONE, Global.current_project.size): - orig_colors.append(layer.get_pixelv(curr_px)) # Color of pixel - var px = curr_px - Global.current_project.selected_pixels[0] - img.set_pixelv(px, orig_colors[i]) - layer.set_pixelv(curr_px, Color(0, 0, 0, 0)) - else: # If part of selection is outside canvas - orig_colors.append(Color(0, 0, 0, 0)) - Global.canvas.update_texture(current_layer_index) - tex.create_from_image(img, 0) - update() - - else: - get_parent().get_parent().mouse_default_cursor_shape = Input.CURSOR_CROSS - - if is_dragging: - if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_pressed("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_pressed("right_mouse")): - # Drag - start_pos.x = orig_x + mouse_pos_floored.x - end_pos.x = diff_x + mouse_pos_floored.x - - start_pos.y = orig_y + mouse_pos_floored.y - end_pos.y = diff_y + mouse_pos_floored.y - polygon[0] = start_pos - polygon[1] = Vector2(end_pos.x, start_pos.y) - polygon[2] = end_pos - polygon[3] = Vector2(start_pos.x, end_pos.y) - - if (Global.current_tools[0] == Global.Tools.RECTSELECT && Input.is_action_just_released("left_mouse")) || (Global.current_tools[1] == Global.Tools.RECTSELECT && Input.is_action_just_released("right_mouse")): - # Release Drag - is_dragging = false - if move_pixels: - for i in range(orig_colors.size()): - if orig_colors[i].a > 0: - var px = polygon[0] + Global.current_project.selected_pixels[i] - Global.current_project.selected_pixels[0] - if point_in_rectangle(px, Global.canvas.location - Vector2.ONE, Global.current_project.size): - layer.set_pixelv(px, orig_colors[i]) - Global.canvas.update_texture(current_layer_index) - img.fill(Color(0, 0, 0, 0)) - tex.create_from_image(img, 0) - update() - - orig_colors.clear() - Global.current_project.selected_pixels.clear() - for xx in range(start_pos.x, end_pos.x): - for yy in range(start_pos.y, end_pos.y): - Global.current_project.selected_pixels.append(Vector2(xx, yy)) - - Global.canvas.handle_redo("Rectangle Select") # Redo - - if Global.current_project.selected_pixels.size() > 0: - # Handle copy - if Input.is_action_just_pressed("copy"): - # Save as custom brush - var brush_img := Image.new() - brush_img = layer.get_rect(Rect2(polygon[0], polygon[2] - polygon[0])) - if brush_img.is_invisible(): - return - brush_img = brush_img.get_rect(brush_img.get_used_rect()) # Save only the visible pixels - Global.current_project.brushes.append(brush_img) - Global.create_brush_button(brush_img) - - # Have it in the clipboard so it can be pasted later - Global.image_clipboard = layer.get_rect(Rect2(polygon[0], polygon[2] - polygon[0])) - - # Handle paste - if Input.is_action_just_pressed("paste") && Global.image_clipboard.get_size() > Vector2.ZERO: - Global.canvas.handle_undo("Draw") - layer.blend_rect(Global.image_clipboard, Rect2(Vector2.ZERO, polygon[2]-polygon[0]), polygon[0]) - layer.lock() - Global.canvas.handle_redo("Draw") - - if Input.is_action_just_pressed("delete"): - Global.canvas.handle_undo("Draw") - for xx in range(start_pos.x, end_pos.x): - for yy in range(start_pos.y, end_pos.y): - if point_in_rectangle(Vector2(xx, yy), Global.canvas.location - Vector2.ONE, Global.canvas.location + Global.current_project.size): - layer.set_pixel(xx, yy, Color(0, 0, 0, 0)) - Global.canvas.handle_redo("Draw") + _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) + _clear_image.fill(Color(0, 0, 0, 0)) func _draw() -> void: - if img.get_size() == polygon[2] - polygon[0]: - draw_texture(tex, polygon[0], Color(1, 1, 1, 0.5)) + if _move_pixel: + draw_texture(_move_texture, _clipped_rect.position, Color(1, 1, 1, 0.5)) -func point_in_rectangle(p : Vector2, coord1 : Vector2, coord2 : Vector2) -> bool: - return p.x > coord1.x && p.y > coord1.y && p.x < coord2.x && p.y < coord2.y +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() + + +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 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) + 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) + 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/Tools/Base.gd b/src/Tools/Base.gd new file mode 100644 index 000000000..b2939db82 --- /dev/null +++ b/src/Tools/Base.gd @@ -0,0 +1,130 @@ +extends VBoxContainer + + +var kname : String +var tool_slot : Tools.Slot = null +var cursor_text := "" + +var _cursor := Vector2.INF + + +func _ready(): + kname = name.replace(" ", "_").to_lower() + $Label.text = tool_slot.name + + yield(get_tree(), "idle_frame") + load_config() + $PixelPerfect.pressed = tool_slot.pixel_perfect + $Mirror/Horizontal.pressed = tool_slot.horizontal_mirror + $Mirror/Vertical.pressed = tool_slot.vertical_mirror + + +func _on_PixelPerfect_toggled(button_pressed : bool): + tool_slot.pixel_perfect = button_pressed + tool_slot.save_config() + + +func _on_Horizontal_toggled(button_pressed : bool): + tool_slot.horizontal_mirror = button_pressed + tool_slot.save_config() + + +func _on_Vertical_toggled(button_pressed : bool): + tool_slot.vertical_mirror = button_pressed + tool_slot.save_config() + + +func save_config() -> void: + var config := get_config() + Global.config_cache.set_value(tool_slot.kname, kname, config) + + +func load_config() -> void: + var value = Global.config_cache.get_value(tool_slot.kname, kname, {}) + set_config(value) + update_config() + + +func get_config() -> Dictionary: + return {} + + +func set_config(_config : Dictionary) -> void: + pass + + +func update_config() -> void: + pass + + +func cursor_move(position : Vector2) -> void: + _cursor = position + + +func draw_indicator() -> void: + var rect := Rect2(_cursor, Vector2.ONE) + Global.canvas.draw_rect(rect, Color.blue, false) + + +func _get_draw_rect() -> Rect2: + var x_min : int = Global.current_project.x_min + var x_max : int = Global.current_project.x_max + var y_min : int = Global.current_project.y_min + var y_max : int = Global.current_project.y_max + return Rect2(x_min, y_min, x_max - x_min, y_max - y_min) + + +func _get_tile_mode_rect() -> Rect2: + return Rect2(-Global.current_project.size, Global.current_project.size * 3) + + +func _get_draw_image() -> Image: + var project : Project = Global.current_project + return project.frames[project.current_frame].cels[project.current_layer].image + + +func _flip_rect(rect : Rect2, size : Vector2, horizontal : bool, vertical : bool) -> Rect2: + var result := rect + if horizontal: + result.position.x = size.x - rect.end.x + result.end.x = size.x - rect.position.x + if vertical: + result.position.y = size.y - rect.end.y + result.end.y = size.y - rect.position.y + return result.abs() + + +func _create_polylines(bitmap : BitMap) -> Array: + var lines := [] + var size := bitmap.get_size() + for y in size.y: + for x in size.x: + var p := Vector2(x, y) + if not bitmap.get_bit(p): + continue + if x <= 0 or not bitmap.get_bit(p - Vector2(1, 0)): + _add_polylines_segment(lines, p, p + Vector2(0, 1)) + if y <= 0 or not bitmap.get_bit(p - Vector2(0, 1)): + _add_polylines_segment(lines, p, p + Vector2(1, 0)) + if x + 1 >= size.x or not bitmap.get_bit(p + Vector2(1, 0)): + _add_polylines_segment(lines, p + Vector2(1, 0), p + Vector2(1, 1)) + if y + 1 >= size.y or not bitmap.get_bit(p + Vector2(0, 1)): + _add_polylines_segment(lines, p + Vector2(0, 1), p + Vector2(1, 1)) + return lines + + +func _add_polylines_segment(lines : Array, start : Vector2, end : Vector2) -> void: + for line in lines: + if line[0] == start: + line.insert(0, end) + return + if line[0] == end: + line.insert(0, start) + return + if line[line.size() - 1] == start: + line.append(end) + return + if line[line.size() - 1] == end: + line.append(start) + return + lines.append([start, end]) diff --git a/src/Tools/Base.tscn b/src/Tools/Base.tscn new file mode 100644 index 000000000..f1bf507d8 --- /dev/null +++ b/src/Tools/Base.tscn @@ -0,0 +1,77 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://src/Tools/Base.gd" type="Script" id=1] +[ext_resource path="res://assets/graphics/dark_themes/tools/horizontal_mirror_on.png" type="Texture" id=2] +[ext_resource path="res://assets/graphics/dark_themes/tools/horizontal_mirror_off.png" type="Texture" id=3] +[ext_resource path="res://assets/graphics/dark_themes/tools/vertical_mirror_on.png" type="Texture" id=4] +[ext_resource path="res://assets/graphics/dark_themes/tools/vertical_mirror_off.png" type="Texture" id=5] + +[node name="ToolOptions" type="VBoxContainer"] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 123.0 +margin_bottom = 65.0 +size_flags_horizontal = 3 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="Label" parent="."] +margin_right = 116.0 +margin_bottom = 14.0 +text = "Tool" +align = 1 +autowrap = true + +[node name="PixelPerfect" type="CheckBox" parent="."] +margin_left = 4.0 +margin_top = 18.0 +margin_right = 112.0 +margin_bottom = 42.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Pixel Perfect" +align = 1 + +[node name="EmptySpacer" type="Control" parent="."] +margin_top = 46.0 +margin_right = 116.0 +margin_bottom = 58.0 +rect_min_size = Vector2( 0, 12 ) + +[node name="Mirror" type="HBoxContainer" parent="."] +margin_top = 62.0 +margin_right = 116.0 +margin_bottom = 79.0 +custom_constants/separation = 44 +alignment = 1 + +[node name="Horizontal" type="TextureButton" parent="Mirror" groups=[ +"UIButtons", +]] +margin_left = 20.0 +margin_right = 35.0 +margin_bottom = 17.0 +hint_tooltip = "Enable horizontal mirrored drawing" +mouse_default_cursor_shape = 2 +toggle_mode = true +texture_normal = ExtResource( 3 ) +texture_pressed = ExtResource( 2 ) + +[node name="Vertical" type="TextureButton" parent="Mirror" groups=[ +"UIButtons", +]] +margin_left = 79.0 +margin_right = 96.0 +margin_bottom = 17.0 +hint_tooltip = "Enable vertical mirrored drawing" +mouse_default_cursor_shape = 2 +toggle_mode = true +texture_normal = ExtResource( 5 ) +texture_pressed = ExtResource( 4 ) +[connection signal="toggled" from="PixelPerfect" to="." method="_on_PixelPerfect_toggled"] +[connection signal="toggled" from="Mirror/Horizontal" to="." method="_on_Horizontal_toggled"] +[connection signal="toggled" from="Mirror/Vertical" to="." method="_on_Vertical_toggled"] diff --git a/src/Tools/Bucket.gd b/src/Tools/Bucket.gd new file mode 100644 index 000000000..e9fc01ec0 --- /dev/null +++ b/src/Tools/Bucket.gd @@ -0,0 +1,204 @@ +extends "res://src/Tools/Base.gd" + + +var _pattern : Patterns.Pattern +var _fill_area := 0 +var _fill_with := 0 +var _offset_x := 0 +var _offset_y := 0 + + +func _ready() -> void: + update_pattern() + + +func _on_FillAreaOptions_item_selected(index : int) -> void: + _fill_area = index + update_config() + save_config() + + +func _on_FillWithOptions_item_selected(index : int) -> void: + _fill_with = index + update_config() + save_config() + + +func _on_PatternType_pressed(): + Global.patterns_popup.connect("pattern_selected", self, "_on_Pattern_selected", [], CONNECT_ONESHOT) + Global.patterns_popup.popup(Rect2($FillPattern/Type.rect_global_position, Vector2(226, 72))) + + +func _on_Pattern_selected(pattern : Patterns.Pattern) -> void: + _pattern = pattern + update_pattern() + save_config() + + +func _on_PatternOffsetX_value_changed(value : float): + _offset_x = int(value) + update_config() + save_config() + + +func _on_PatternOffsetY_value_changed(value : float): + _offset_y = int(value) + update_config() + save_config() + + +func get_config() -> Dictionary: + return { + "pattern_index" : _pattern.index, + "fill_area" : _fill_area, + "fill_with" : _fill_with, + "offset_x" : _offset_x, + "offset_y" : _offset_y, + } + + +func set_config(config : Dictionary) -> void: + var index = config.get("pattern_index", _pattern.index) + _pattern = Global.patterns_popup.get_pattern(index) + _fill_area = config.get("fill_area", _fill_area) + _fill_with = config.get("fill_with", _fill_with) + _offset_x = config.get("offset_x", _offset_x) + _offset_y = config.get("offset_y", _offset_y) + update_pattern() + + +func update_config() -> void: + $FillAreaOptions.selected = _fill_area + $FillWithOptions.selected = _fill_with + $Mirror.visible = _fill_area == 0 + $FillPattern.visible = _fill_with == 1 + $FillPattern/XOffset/OffsetX.value = _offset_x + $FillPattern/YOffset/OffsetY.value = _offset_y + + +func update_pattern() -> void: + if _pattern == null: + _pattern = Global.patterns_popup.default_pattern + var tex := ImageTexture.new() + tex.create_from_image(_pattern.image, 0) + $FillPattern/Type/Texture.texture = tex + var size := _pattern.image.get_size() + $FillPattern/XOffset/OffsetX.max_value = size.x - 1 + $FillPattern/YOffset/OffsetY.max_value = size.y - 1 + + +func draw_start(position : Vector2) -> void: + if not _get_draw_rect().has_point(position): + return + var undo_data = _get_undo_data() + if _fill_area == 0: + fill_in_area(position) + else: + fill_in_color(position) + commit_undo("Draw", undo_data) + + +func draw_move(_position : Vector2) -> void: + pass + + +func draw_end(_position : Vector2) -> void: + pass + + +func fill_in_color(position : Vector2) -> void: + var project : Project = Global.current_project + var image := _get_draw_image() + var color := image.get_pixelv(position) + if _fill_with == 0 or _pattern == null: + if tool_slot.color.is_equal_approx(color): + return + + image.lock() + for y in range(project.y_min, project.y_max): + for x in range(project.x_min, project.x_max): + if image.get_pixel(x, y).is_equal_approx(color): + _set_pixel(image, x, y, tool_slot.color) + + +func fill_in_area(position : Vector2) -> void: + var project : Project = Global.current_project + var mirror_x := project.x_max + project.x_min - position.x - 1 + var mirror_y := project.y_max + project.y_min - position.y - 1 + + _flood_fill(position) + if tool_slot.horizontal_mirror: + _flood_fill(Vector2(mirror_x, position.y)) + if tool_slot.vertical_mirror: + _flood_fill(Vector2(mirror_x, mirror_y)) + if tool_slot.vertical_mirror: + _flood_fill(Vector2(position.x, mirror_y)) + + +func _flood_fill(position : Vector2) -> void: + var project : Project = Global.current_project + var image := _get_draw_image() + var color := image.get_pixelv(position) + if _fill_with == 0 or _pattern == null: + if tool_slot.color.is_equal_approx(color): + return + + image.lock() + var processed := BitMap.new() + processed.create(image.get_size()) + var q = [position] + for n in q: + if processed.get_bit(n): + continue + var west : Vector2 = n + var east : Vector2 = n + while west.x >= project.x_min && image.get_pixelv(west).is_equal_approx(color): + west += Vector2.LEFT + while east.x < project.x_max && image.get_pixelv(east).is_equal_approx(color): + east += Vector2.RIGHT + for px in range(west.x + 1, east.x): + var p := Vector2(px, n.y) + _set_pixel(image, p.x, p.y, tool_slot.color) + processed.set_bit(p, true) + var north := p + Vector2.UP + var south := p + Vector2.DOWN + if north.y >= project.y_min && image.get_pixelv(north).is_equal_approx(color): + q.append(north) + if south.y < project.y_max && image.get_pixelv(south).is_equal_approx(color): + q.append(south) + + +func _set_pixel(image : Image, x : int, y : int, color : Color) -> void: + if _fill_with == 0 or _pattern == null: + image.set_pixel(x, y, color) + else: + _pattern.image.lock() + var size := _pattern.image.get_size() + var px := int(x + _offset_x) % int(size.x) + var py := int(y + _offset_y) % int(size.y) + var pc := _pattern.image.get_pixel(px, py) + image.set_pixel(x, y, pc) + + +func commit_undo(action : String, undo_data : Dictionary) -> void: + var redo_data = _get_undo_data() + var project : Project = Global.current_project + var image : Image = project.frames[project.current_frame].cels[project.current_layer].image + + project.undos += 1 + project.undo_redo.create_action(action) + 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() -> Dictionary: + var data = {} + var project : Project = Global.current_project + 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/Tools/Bucket.tscn b/src/Tools/Bucket.tscn new file mode 100644 index 000000000..d5e7ebe72 --- /dev/null +++ b/src/Tools/Bucket.tscn @@ -0,0 +1,144 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://assets/graphics/brush_button.png" type="Texture" id=1] +[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=2] +[ext_resource path="res://src/Tools/Bucket.gd" type="Script" id=3] + +[node name="ToolOptions" instance=ExtResource( 2 )] +script = ExtResource( 3 ) + +[node name="Label" parent="." index="0"] +margin_right = 131.0 + +[node name="FillArea" type="Label" parent="." index="1"] +margin_left = 38.0 +margin_top = 18.0 +margin_right = 92.0 +margin_bottom = 32.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Fill area:" + +[node name="FillAreaOptions" type="OptionButton" parent="." index="2"] +margin_top = 36.0 +margin_right = 131.0 +margin_bottom = 56.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Same color area" +items = [ "Same color area", null, false, 0, null, "Same color pixels", null, false, 1, null ] +selected = 0 + +[node name="FillWith" type="Label" parent="." index="3"] +margin_left = 38.0 +margin_top = 60.0 +margin_right = 92.0 +margin_bottom = 74.0 +size_flags_horizontal = 4 +text = "Fill with:" + +[node name="FillWithOptions" type="OptionButton" parent="." index="4"] +margin_left = 5.0 +margin_top = 78.0 +margin_right = 126.0 +margin_bottom = 98.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Selected Color" +items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ] +selected = 0 + +[node name="FillPattern" type="VBoxContainer" parent="." index="5"] +visible = false +margin_left = 22.0 +margin_top = 102.0 +margin_right = 108.0 +margin_bottom = 208.0 +size_flags_horizontal = 4 + +[node name="Type" type="TextureButton" parent="FillPattern" index="0"] +margin_left = 27.0 +margin_right = 59.0 +margin_bottom = 32.0 +hint_tooltip = "Select a brush" +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +texture_normal = ExtResource( 1 ) + +[node name="Texture" type="TextureRect" parent="FillPattern/Type" index="0"] +margin_right = 32.0 +margin_bottom = 32.0 +expand = true +stretch_mode = 6 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Offset" type="Label" parent="FillPattern" index="1"] +margin_top = 36.0 +margin_right = 86.0 +margin_bottom = 50.0 +text = "Offset" +align = 1 + +[node name="XOffset" type="HBoxContainer" parent="FillPattern" index="2"] +margin_top = 54.0 +margin_right = 86.0 +margin_bottom = 78.0 + +[node name="Label" type="Label" parent="FillPattern/XOffset" index="0"] +margin_top = 5.0 +margin_right = 8.0 +margin_bottom = 19.0 +text = "X" + +[node name="OffsetX" type="SpinBox" parent="FillPattern/XOffset" index="1"] +margin_left = 12.0 +margin_right = 86.0 +margin_bottom = 24.0 +mouse_default_cursor_shape = 2 + +[node name="YOffset" type="HBoxContainer" parent="FillPattern" index="3"] +margin_top = 82.0 +margin_right = 86.0 +margin_bottom = 106.0 + +[node name="Label" type="Label" parent="FillPattern/YOffset" index="0"] +margin_top = 5.0 +margin_right = 7.0 +margin_bottom = 19.0 +text = "Y" + +[node name="OffsetY" type="SpinBox" parent="FillPattern/YOffset" index="1"] +margin_left = 11.0 +margin_right = 85.0 +margin_bottom = 24.0 +mouse_default_cursor_shape = 2 + +[node name="PixelPerfect" parent="." index="6"] +visible = false + +[node name="EmptySpacer" parent="." index="7"] +visible = false +margin_top = 212.0 +margin_right = 131.0 +margin_bottom = 224.0 + +[node name="Mirror" parent="." index="8"] +visible = false +margin_top = 212.0 +margin_right = 131.0 +margin_bottom = 229.0 + +[node name="Horizontal" parent="Mirror" index="0"] +margin_left = 27.0 +margin_right = 42.0 + +[node name="Vertical" parent="Mirror" index="1"] +margin_left = 86.0 +margin_right = 103.0 +[connection signal="item_selected" from="FillAreaOptions" to="." method="_on_FillAreaOptions_item_selected"] +[connection signal="item_selected" from="FillWithOptions" to="." method="_on_FillWithOptions_item_selected"] +[connection signal="pressed" from="FillPattern/Type" to="." method="_on_PatternType_pressed"] +[connection signal="value_changed" from="FillPattern/XOffset/OffsetX" to="." method="_on_PatternOffsetX_value_changed"] +[connection signal="value_changed" from="FillPattern/YOffset/OffsetY" to="." method="_on_PatternOffsetY_value_changed"] diff --git a/src/Tools/ColorPicker.gd b/src/Tools/ColorPicker.gd new file mode 100644 index 000000000..5b8b39675 --- /dev/null +++ b/src/Tools/ColorPicker.gd @@ -0,0 +1,45 @@ +extends "res://src/Tools/Base.gd" + + +var _color_slot := 0 + + +func _on_Options_item_selected(id): + _color_slot = id + update_config() + save_config() + + +func get_config() -> Dictionary: + return { + "color_slot" : _color_slot, + } + + +func set_config(config : Dictionary) -> void: + _color_slot = config.get("color_slot", _color_slot) + + +func update_config() -> void: + $ColorPicker/Options.selected = _color_slot + + +func draw_start(position : Vector2) -> void: + _pick_color(position) + + +func draw_move(position : Vector2) -> void: + _pick_color(position) + + +func draw_end(_position : Vector2) -> void: + pass + + +func _pick_color(position : Vector2) -> void: + var image := Image.new() + image.copy_from(_get_draw_image()) + image.lock() + var color := image.get_pixelv(position) + var button := BUTTON_LEFT if _color_slot == 0 else BUTTON_RIGHT + Tools.assign_color(color, button) diff --git a/src/Tools/ColorPicker.tscn b/src/Tools/ColorPicker.tscn new file mode 100644 index 000000000..c230814c5 --- /dev/null +++ b/src/Tools/ColorPicker.tscn @@ -0,0 +1,47 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/ColorPicker.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="ColorPicker" type="VBoxContainer" parent="." index="1"] +margin_top = 18.0 +margin_right = 116.0 +margin_bottom = 56.0 + +[node name="Label" type="Label" parent="ColorPicker" index="0"] +margin_left = 32.0 +margin_right = 83.0 +margin_bottom = 14.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Pick for:" + +[node name="Options" type="OptionButton" parent="ColorPicker" index="1"] +margin_left = 13.0 +margin_top = 18.0 +margin_right = 103.0 +margin_bottom = 38.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Left Color" +items = [ "Left Color", null, false, 0, null, "Right Color", null, false, 1, null ] +selected = 0 + +[node name="PixelPerfect" parent="." index="2"] +visible = false +margin_top = 60.0 +margin_bottom = 84.0 + +[node name="EmptySpacer" parent="." index="3"] +visible = false +margin_top = 60.0 +margin_bottom = 72.0 + +[node name="Mirror" parent="." index="4"] +visible = false +margin_top = 60.0 +margin_bottom = 77.0 +[connection signal="item_selected" from="ColorPicker/Options" to="." method="_on_Options_item_selected"] diff --git a/src/Tools/Draw.gd b/src/Tools/Draw.gd new file mode 100644 index 000000000..106c070b5 --- /dev/null +++ b/src/Tools/Draw.gd @@ -0,0 +1,494 @@ +extends "res://src/Tools/Base.gd" + + +var _brush := Brushes.get_default_brush() +var _brush_size := 1 +var _brush_interpolate := 0 +var _brush_image := Image.new() +var _brush_texture := ImageTexture.new() +var _strength := 1.0 + +var _undo_data := {} +var _drawer := Drawer.new() +var _mask := PoolByteArray() +var _mirror_brushes := {} + +var _draw_line := false +var _line_start := Vector2.ZERO +var _line_end := Vector2.ZERO + +var _indicator := BitMap.new() +var _polylines := [] +var _line_polylines := [] + +func _ready() -> void: + Tools.connect("color_changed", self, "_on_Color_changed") + Global.brushes_popup.connect("brush_removed", self, "_on_Brush_removed") + + +func _on_BrushType_pressed() -> void: + if not Global.brushes_popup.is_connected("brush_selected", self, "_on_Brush_selected"): + Global.brushes_popup.connect("brush_selected", self, "_on_Brush_selected", [], CONNECT_ONESHOT) + Global.brushes_popup.popup(Rect2($Brush/Type.rect_global_position, Vector2(226, 72))) + + +func _on_Brush_selected(brush : Brushes.Brush) -> void: + _brush = brush + update_brush() + save_config() + + +func _on_BrushSize_value_changed(value : float) -> void: + _brush_size = int(value) + update_config() + save_config() + + +func _on_InterpolateFactor_value_changed(value : float) -> void: + _brush_interpolate = int(value) + update_config() + save_config() + + +func _on_Color_changed(_color : Color, _button : int) -> void: + update_brush() + + +func _on_Brush_removed(brush : Brushes.Brush) -> void: + if brush == _brush: + _brush = Brushes.get_default_brush() + update_brush() + save_config() + + +func get_config() -> Dictionary: + return { + "brush_type" : _brush.type, + "brush_index" : _brush.index, + "brush_size" : _brush_size, + "brush_interpolate" : _brush_interpolate, + } + + +func set_config(config : Dictionary) -> void: + var type = config.get("brush_type", _brush.type) + var index = config.get("brush_index", _brush.index) + _brush = Global.brushes_popup.get_brush(type, index) + _brush_size = config.get("brush_size", _brush_size) + _brush_interpolate = config.get("brush_interpolate", _brush_interpolate) + + +func update_config() -> void: + $Brush/Size.value = _brush_size + $BrushSize.value = _brush_size + $ColorInterpolation/Factor.value = _brush_interpolate + $ColorInterpolation/Slider.value = _brush_interpolate + update_brush() + + +func update_brush() -> void: + match _brush.type: + Brushes.PIXEL: + _brush_texture.create_from_image(load("res://assets/graphics/pixel_image.png"), 0) + Brushes.CIRCLE: + _brush_texture.create_from_image(load("res://assets/graphics/circle_9x9.png"), 0) + Brushes.FILLED_CIRCLE: + _brush_texture.create_from_image(load("res://assets/graphics/circle_filled_9x9.png"), 0) + Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM: + if _brush.random.size() <= 1: + _brush_image = _create_blended_brush_image(_brush.image) + 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() + _polylines = _create_polylines(_indicator) + $Brush/Type/Texture.texture = _brush_texture + $ColorInterpolation.visible = _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM] + + +func update_random_image() -> void: + if _brush.type != Brushes.RANDOM_FILE: + 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() + + +func update_mirror_brush() -> void: + _mirror_brushes.x = _brush_image.duplicate() + _mirror_brushes.x.flip_x() + _mirror_brushes.y = _brush_image.duplicate() + _mirror_brushes.y.flip_y() + _mirror_brushes.xy = _mirror_brushes.x.duplicate() + _mirror_brushes.xy.flip_y() + + +func update_mask() -> void: + var size := _get_draw_image().get_size() + _mask = PoolByteArray() + _mask.resize(size.x * size.y) + for i in _mask.size(): + _mask[i] = 0 + + +func update_line_polylines(start : Vector2, end : Vector2) -> void: + var indicator := _create_line_indicator(_indicator, start, end) + _line_polylines = _create_polylines(indicator) + + +func restore_image() -> void: + var project : Project = Global.current_project + var image = project.frames[project.current_frame].cels[project.current_layer].image + image.unlock() + image.data = _undo_data[image] + image.lock() + + +func prepare_undo() -> void: + _undo_data = _get_undo_data() + + +func commit_undo(action : String) -> void: + var redo_data = _get_undo_data() + var project : Project = Global.current_project + var frame := -1 + var layer := -1 + if Global.animation_timer.is_stopped(): + frame = project.current_frame + layer = project.current_layer + + project.undos += 1 + project.undo_redo.create_action(action) + for image in redo_data: + project.undo_redo.add_do_property(image, "data", redo_data[image]) + for image in _undo_data: + project.undo_redo.add_undo_property(image, "data", _undo_data[image]) + project.undo_redo.add_do_method(Global, "redo", frame, layer) + project.undo_redo.add_undo_method(Global, "undo", frame, layer) + project.undo_redo.commit_action() + + _undo_data.clear() + + +func draw_tool(position : Vector2) -> void: + var strength := _strength + if Global.pressure_sensitivity_mode == Global.Pressure_Sensitivity.ALPHA: + strength *= Tools.pen_pressure + + _drawer.pixel_perfect = tool_slot.pixel_perfect if _brush_size == 1 else false + _drawer.horizontal_mirror = tool_slot.horizontal_mirror + _drawer.vertical_mirror = tool_slot.vertical_mirror + _drawer.color_op.strength = strength + + match _brush.type: + Brushes.PIXEL: + draw_tool_pixel(position) + Brushes.CIRCLE: + draw_tool_circle(position, false) + Brushes.FILLED_CIRCLE: + draw_tool_circle(position, true) + _: + draw_tool_brush(position) + + +# Bresenham's Algorithm +# Thanks to https://godotengine.org/qa/35276/tile-based-line-drawing-algorithm-efficiency +func draw_fill_gap(start : Vector2, end : Vector2) -> void: + var dx := int(abs(end.x - start.x)) + var dy := int(-abs(end.y - start.y)) + var err := dx + dy + var e2 := err << 1 + var sx = 1 if start.x < end.x else -1 + var sy = 1 if start.y < end.y else -1 + var x = start.x + var y = start.y + while !(x == end.x && y == end.y): + e2 = err << 1 + if e2 >= dy: + err += dy + x += sx + if e2 <= dx: + err += dx + y += sy + draw_tool(Vector2(x, y)) + + +func draw_tool_pixel(position : Vector2) -> void: + var start := position - Vector2.ONE * (_brush_size >> 1) + var end := start + Vector2.ONE * _brush_size + for y in range(start.y, end.y): + for x in range(start.x, end.x): + _set_pixel(Vector2(x, y)) + + +# Algorithm based on http://members.chello.at/easyfilter/bresenham.html +func draw_tool_circle(position : Vector2, fill := false) -> void: + var r := _brush_size + var x := -r + var y := 0 + var err := 2 - r * 2 + var draw := true + if fill: + _set_pixel(position) + while x < 0: + if draw: + for i in range(1 if fill else -x, -x + 1): + _set_pixel(position + Vector2(-i, y)) + _set_pixel(position + Vector2(-y, -i)) + _set_pixel(position + Vector2(i, -y)) + _set_pixel(position + Vector2(y, i)) + draw = not fill + r = err + if r <= y: + y += 1 + err += y * 2 + 1 + draw = true + if r > x || err > y: + x += 1 + err += x * 2 + 1 + + +func draw_tool_brush(position : Vector2) -> void: + if Global.tile_mode and _get_tile_mode_rect().has_point(position): + position = position.posmodv(Global.current_project.size) + + var size := _brush_image.get_size() + var dst := position - (size / 2).floor() + var dst_rect := Rect2(dst, size) + var draw_rect := _get_draw_rect() + dst_rect = dst_rect.clip(draw_rect) + if dst_rect.size == Vector2.ZERO: + return + var src_rect := Rect2(dst_rect.position - dst, dst_rect.size) + dst = dst_rect.position + + var mirror_x = draw_rect.end.x + draw_rect.position.x - dst.x - src_rect.size.x + var mirror_y = draw_rect.end.y + draw_rect.position.y - dst.y - src_rect.size.y + + _draw_brush_image(_brush_image, src_rect, dst) + if tool_slot.horizontal_mirror: + _draw_brush_image(_mirror_brushes.x, _flip_rect(src_rect, size, true, false), Vector2(mirror_x, dst.y)) + if tool_slot.vertical_mirror: + _draw_brush_image(_mirror_brushes.xy, _flip_rect(src_rect, size, true, true), Vector2(mirror_x, mirror_y)) + if tool_slot.vertical_mirror: + _draw_brush_image(_mirror_brushes.y, _flip_rect(src_rect, size, false, true), Vector2(dst.x, mirror_y)) + + +func draw_indicator() -> void: + draw_indicator_at(_cursor, Vector2.ZERO, Color.blue) + if Global.tile_mode and _get_tile_mode_rect().has_point(_cursor): + var tile := _line_start if _draw_line else _cursor + if not _get_draw_rect().has_point(tile): + var offset := tile - tile.posmodv(Global.current_project.size) + draw_indicator_at(_cursor, offset, Color.green) + + +func draw_indicator_at(position : Vector2, offset : Vector2, color : Color) -> void: + var canvas = Global.canvas + if _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM] and not _draw_line: + position -= (_brush_image.get_size() / 2).floor() + position -= offset + canvas.draw_texture(_brush_texture, position) + else: + if _draw_line: + position.x = _line_end.x if _line_end.x < _line_start.x else _line_start.x + position.y = _line_end.y if _line_end.y < _line_start.y else _line_start.y + position -= (_indicator.get_size() / 2).floor() + position -= offset + canvas.draw_set_transform(position, canvas.rotation, canvas.scale) + var polylines := _line_polylines if _draw_line else _polylines + for line in polylines: + var pool := PoolVector2Array(line) + canvas.draw_polyline(pool, color) + canvas.draw_set_transform(canvas.position, canvas.rotation, canvas.scale) + + +func _set_pixel(position : Vector2) -> void: + if Global.tile_mode and _get_tile_mode_rect().has_point(position): + position = position.posmodv(Global.current_project.size) + + if not _get_draw_rect().has_point(position): + return + + var image := _get_draw_image() + var i := int(position.x + position.y * image.get_size().x) + if _mask[i] < Tools.pen_pressure: + _mask[i] = Tools.pen_pressure + _drawer.set_pixel(image, position, tool_slot.color) + + +func _draw_brush_image(_image : Image, _src_rect: Rect2, _dst: Vector2) -> void: + pass + + +func _create_blended_brush_image(image : Image) -> Image: + var size := image.get_size() * _brush_size + var brush := Image.new() + brush.copy_from(image) + brush = _blend_image(brush, tool_slot.color, _brush_interpolate / 100.0) + brush.unlock() + brush.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + return brush + + +func _blend_image(image : Image, color : Color, factor : float) -> Image: + var size := image.get_size() + image.lock() + for y in size.y: + for x in size.x: + var color_old := image.get_pixel(x, y) + if color_old.a > 0: + var color_new := color_old.linear_interpolate(color, factor) + color_new.a = color_old.a + image.set_pixel(x, y, color_new) + return image + + +func _create_brush_indicator() -> BitMap: + match _brush.type: + Brushes.PIXEL: + return _create_pixel_indicator(_brush_size) + Brushes.CIRCLE: + return _create_circle_indicator(_brush_size, false) + Brushes.FILLED_CIRCLE: + return _create_circle_indicator(_brush_size, true) + _: + return _create_image_indicator(_brush_image) + + +func _create_image_indicator(image : Image) -> BitMap: + var bitmap := BitMap.new() + bitmap.create_from_image_alpha(image, 0.0) + return bitmap + + +func _create_pixel_indicator(size : int) -> BitMap: + var bitmap := BitMap.new() + bitmap.create(Vector2.ONE * size) + bitmap.set_bit_rect(Rect2(Vector2.ZERO, Vector2.ONE * size), true) + return bitmap + + +func _create_circle_indicator(size : int, fill := false) -> BitMap: + var bitmap := BitMap.new() + bitmap.create(Vector2.ONE * (size * 2 + 1)) + var position := Vector2(size, size) + + var r := size + var x := -r + var y := 0 + var err := 2 - r * 2 + var draw := true + if fill: + bitmap.set_bit(position, true) + while x < 0: + if draw: + for i in range(1 if fill else -x, -x + 1): + bitmap.set_bit(position + Vector2(-i, y), true) + bitmap.set_bit(position + Vector2(-y, -i), true) + bitmap.set_bit(position + Vector2(i, -y), true) + bitmap.set_bit(position + Vector2(y, i), true) + draw = not fill + r = err + if r <= y: + y += 1 + err += y * 2 + 1 + draw = true + if r > x || err > y: + x += 1 + err += x * 2 + 1 + return bitmap + + +func _create_line_indicator(indicator : BitMap, start : Vector2, end : Vector2) -> BitMap: + var bitmap := BitMap.new() + var size := (end - start).abs() + indicator.get_size() + bitmap.create(size) + + var offset := (indicator.get_size() / 2).floor() + var diff := end - start + start.x = -diff.x if diff.x < 0 else 0.0 + end.x = 0.0 if diff.x < 0 else diff.x + start.y = -diff.y if diff.y < 0 else 0.0 + end.y = 0.0 if diff.y < 0 else diff.y + start += offset + end += offset + + var dx := int(abs(end.x - start.x)) + var dy := int(-abs(end.y - start.y)) + var err := dx + dy + var e2 := err << 1 + var sx = 1 if start.x < end.x else -1 + var sy = 1 if start.y < end.y else -1 + var x = start.x + var y = start.y + while !(x == end.x && y == end.y): + _blit_indicator(bitmap, indicator, Vector2(x, y)) + e2 = err << 1 + if e2 >= dy: + err += dy + x += sx + if e2 <= dx: + err += dx + y += sy + _blit_indicator(bitmap, indicator, Vector2(x, y)) + return bitmap + + +func _blit_indicator(dst : BitMap, indicator : BitMap, position : Vector2) -> void: + var rect := Rect2(Vector2.ZERO, dst.get_size()) + var size := indicator.get_size() + position -= (size / 2).floor() + for y in size.y: + for x in size.x: + var pos := Vector2(x, y) + var bit := indicator.get_bit(pos) + pos += position + if bit and rect.has_point(pos): + dst.set_bit(pos, bit) + + +func _line_angle_constraint(start : Vector2, end : Vector2) -> Dictionary: + var result := {} + var angle := rad2deg(end.angle_to_point(start)) + var distance := start.distance_to(end) + if Tools.control: + if tool_slot.pixel_perfect: + angle = stepify(angle, 22.5) + if step_decimals(angle) != 0: + var diff := end - start + var v := Vector2(2 , 1) if abs(diff.x) > abs(diff.y) else Vector2(1 , 2) + var p := diff.project(diff.sign() * v).abs().round() + var f := p.y if abs(diff.x) > abs(diff.y) else p.x + end = start + diff.sign() * v * f - diff.sign() + angle = rad2deg(atan2(sign(diff.y) * v.y, sign(diff.x) * v.x)) + else: + end = start + Vector2.RIGHT.rotated(deg2rad(angle)) * distance + else: + angle = stepify(angle, 15) + end = start + Vector2.RIGHT.rotated(deg2rad(angle)) * distance + angle *= -1 + angle += 360 if angle < 0 else 0 + result.text = str(stepify(angle, 0.01)) + "°" + result.position = end.round() + return result + + +func _get_undo_data() -> Dictionary: + var data = {} + var project : Project = Global.current_project + var frames := project.frames + if Global.animation_timer.is_stopped(): + frames = [project.frames[project.current_frame]] + for frame in frames: + var image : Image = frame.cels[project.current_layer].image + image.unlock() + data[image] = image.data + image.lock() + return data diff --git a/src/Tools/Draw.tscn b/src/Tools/Draw.tscn new file mode 100644 index 000000000..060e3e911 --- /dev/null +++ b/src/Tools/Draw.tscn @@ -0,0 +1,114 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://assets/graphics/brush_button.png" type="Texture" id=1] +[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=2] +[ext_resource path="res://src/Tools/Draw.gd" type="Script" id=3] + +[node name="ToolOptions" instance=ExtResource( 2 )] +script = ExtResource( 3 ) + +[node name="Brush" type="HBoxContainer" parent="." index="1"] +margin_top = 18.0 +margin_right = 116.0 +margin_bottom = 50.0 +alignment = 1 + +[node name="Type" type="TextureButton" parent="Brush" index="0"] +margin_left = 1.0 +margin_right = 37.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 36, 32 ) +hint_tooltip = "Select a brush" +mouse_default_cursor_shape = 2 +size_flags_horizontal = 0 +texture_normal = ExtResource( 1 ) + +[node name="Texture" type="TextureRect" parent="Brush/Type" index="0"] +margin_right = 32.0 +margin_bottom = 32.0 +expand = true +stretch_mode = 6 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Size" type="SpinBox" parent="Brush" index="1"] +margin_left = 41.0 +margin_right = 115.0 +margin_bottom = 32.0 +mouse_default_cursor_shape = 2 +min_value = 1.0 +value = 1.0 +align = 1 +suffix = "px" + +[node name="BrushSize" type="HSlider" parent="." index="2"] +margin_left = 12.0 +margin_top = 54.0 +margin_right = 104.0 +margin_bottom = 70.0 +rect_min_size = Vector2( 92, 0 ) +focus_mode = 0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +size_flags_vertical = 1 +min_value = 1.0 +value = 1.0 +allow_greater = true +ticks_on_borders = true + +[node name="PixelPerfect" parent="." index="3"] +margin_top = 74.0 +margin_bottom = 98.0 + +[node name="ColorInterpolation" type="VBoxContainer" parent="." index="4"] +visible = false +margin_top = 102.0 +margin_right = 116.0 +margin_bottom = 164.0 +alignment = 1 + +[node name="Label" type="Label" parent="ColorInterpolation" index="0"] +margin_left = 4.0 +margin_right = 111.0 +margin_bottom = 14.0 +hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" +mouse_filter = 1 +size_flags_horizontal = 4 +text = "Brush color from" + +[node name="Factor" type="SpinBox" parent="ColorInterpolation" index="1"] +margin_left = 21.0 +margin_top = 18.0 +margin_right = 95.0 +margin_bottom = 42.0 +hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +align = 1 + +[node name="Slider" type="HSlider" parent="ColorInterpolation" index="2"] +margin_left = 12.0 +margin_top = 46.0 +margin_right = 104.0 +margin_bottom = 62.0 +rect_min_size = Vector2( 92, 0 ) +hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" +focus_mode = 0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +size_flags_vertical = 1 +ticks_on_borders = true + +[node name="EmptySpacer" parent="." index="5"] +margin_top = 102.0 +margin_bottom = 114.0 + +[node name="Mirror" parent="." index="6"] +margin_top = 118.0 +margin_bottom = 135.0 +[connection signal="pressed" from="Brush/Type" to="." method="_on_BrushType_pressed"] +[connection signal="value_changed" from="Brush/Size" to="." method="_on_BrushSize_value_changed"] +[connection signal="value_changed" from="BrushSize" to="." method="_on_BrushSize_value_changed"] +[connection signal="value_changed" from="ColorInterpolation/Factor" to="." method="_on_InterpolateFactor_value_changed"] +[connection signal="value_changed" from="ColorInterpolation/Slider" to="." method="_on_InterpolateFactor_value_changed"] diff --git a/src/Tools/Eraser.gd b/src/Tools/Eraser.gd new file mode 100644 index 000000000..b73ff86a9 --- /dev/null +++ b/src/Tools/Eraser.gd @@ -0,0 +1,75 @@ +extends "res://src/Tools/Draw.gd" + + +var _last_position := Vector2.INF +var _clear_image := Image.new() +var _changed := false + + +class EraseOp extends Drawer.ColorOp: + var changed := false + + + func process(_src: Color, _dst: Color) -> Color: + changed = true +# dst.a -= src.a * strength +# return dst + return Color(0, 0, 0, 0) + + +func _init() -> void: + _drawer.color_op = EraseOp.new() + _clear_image.create(1, 1, false, Image.FORMAT_RGBA8) + _clear_image.fill(Color(0, 0, 0, 0)) + + +func draw_start(position : Vector2) -> void: + update_mask() + _changed = false + _drawer.color_op.changed = false + + prepare_undo() + _drawer.reset() + + _draw_line = Tools.shift + if _draw_line: + _line_start = position + _line_end = position + update_line_polylines(_line_start, _line_end) + else: + draw_tool(position) + _last_position = position + Global.canvas.sprite_changed_this_frame = true + cursor_text = "" + + +func draw_move(position : Vector2) -> void: + if _draw_line: + var d = _line_angle_constraint(_line_start, position) + _line_end = d.position + cursor_text = d.text + update_line_polylines(_line_start, _line_end) + else: + draw_fill_gap(_last_position, position) + _last_position = position + cursor_text = "" + Global.canvas.sprite_changed_this_frame = true + + +func draw_end(_position : Vector2) -> void: + if _draw_line: + draw_tool(_line_start) + draw_fill_gap(_line_start, _line_end) + _draw_line = false + if _changed or _drawer.color_op.changed: + commit_undo("Draw") + cursor_text = "" + update_random_image() + + +func _draw_brush_image(_image : Image, src_rect: Rect2, dst: Vector2) -> void: + _changed = true + var size := _image.get_size() + if _clear_image.get_size() != size: + _clear_image.resize(size.x, size.y, Image.INTERPOLATE_NEAREST) + _get_draw_image().blit_rect_mask(_clear_image, _image, src_rect, dst) diff --git a/src/Tools/Eraser.tscn b/src/Tools/Eraser.tscn new file mode 100644 index 000000000..52ed98ef7 --- /dev/null +++ b/src/Tools/Eraser.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/Draw.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/Eraser.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) diff --git a/src/Tools/LightenDarken.gd b/src/Tools/LightenDarken.gd new file mode 100644 index 000000000..31c328b72 --- /dev/null +++ b/src/Tools/LightenDarken.gd @@ -0,0 +1,112 @@ +extends "res://src/Tools/Draw.gd" + + +var _last_position := Vector2.INF +var _changed := false +var _mode := 0 +var _amount := 10 + + +class LightenDarkenOp extends Drawer.ColorOp: + var changed := false + + + func process(_src: Color, dst: Color) -> Color: + changed = true + if strength > 0: + return dst.lightened(strength) + elif strength < 0: + return dst.darkened(-strength) + else: + return dst + + +func _init() -> void: + _drawer.color_op = LightenDarkenOp.new() + + +func _on_LightenDarken_item_selected(id : int): + _mode = id + update_config() + save_config() + + +func _on_LightenDarken_value_changed(value : float): + _amount = int(value) + update_config() + save_config() + + +func get_config() -> Dictionary: + var config := .get_config() + config["mode"] = _mode + config["amount"] = _amount + return config + + +func set_config(config : Dictionary) -> void: + .set_config(config) + _mode = config.get("mode", _mode) + _amount = config.get("amount", _amount) + + +func update_config() -> void: + .update_config() + $LightenDarken.selected = _mode + $Amount/Spinbox.value = _amount + $Amount/Slider.value = _amount + update_strength() + + +func update_strength() -> void: + var factor = 1 if _mode == 0 else -1 + _strength = _amount * factor / 100.0 + + +func draw_start(position : Vector2) -> void: + update_mask() + _changed = false + _drawer.color_op.changed = false + + prepare_undo() + _drawer.reset() + + _draw_line = Tools.shift + if _draw_line: + _line_start = position + _line_end = position + update_line_polylines(_line_start, _line_end) + else: + draw_tool(position) + _last_position = position + Global.canvas.sprite_changed_this_frame = true + cursor_text = "" + + +func draw_move(position : Vector2) -> void: + if _draw_line: + var d = _line_angle_constraint(_line_start, position) + _line_end = d.position + cursor_text = d.text + update_line_polylines(_line_start, _line_end) + else: + draw_fill_gap(_last_position, position) + _last_position = position + cursor_text = "" + Global.canvas.sprite_changed_this_frame = true + + +func draw_end(_position : Vector2) -> void: + if _draw_line: + draw_tool(_line_start) + draw_fill_gap(_line_start, _line_end) + _draw_line = false + if _changed or _drawer.color_op.changed: + commit_undo("Draw") + cursor_text = "" + update_random_image() + + +func _draw_brush_image(_image : Image, _src_rect: Rect2, _dst: Vector2) -> void: + _changed = true + draw_tool_pixel(_cursor.floor()) diff --git a/src/Tools/LightenDarken.tscn b/src/Tools/LightenDarken.tscn new file mode 100644 index 000000000..7f781fdcd --- /dev/null +++ b/src/Tools/LightenDarken.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/Draw.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/LightenDarken.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="LightenDarken" type="OptionButton" parent="." index="4"] +margin_left = 12.0 +margin_top = 102.0 +margin_right = 104.0 +margin_bottom = 122.0 +rect_min_size = Vector2( 92, 0 ) +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Lighten" +items = [ "Lighten", null, false, 0, null, "Darken", null, false, 1, null ] +selected = 0 + +[node name="Amount" type="VBoxContainer" parent="." index="5"] +margin_top = 126.0 +margin_right = 116.0 +margin_bottom = 188.0 +alignment = 1 + +[node name="Label" type="Label" parent="Amount" index="0"] +margin_left = 30.0 +margin_right = 85.0 +margin_bottom = 14.0 +size_flags_horizontal = 4 +text = "Amount:" + +[node name="Spinbox" type="SpinBox" parent="Amount" index="1"] +margin_left = 21.0 +margin_top = 18.0 +margin_right = 95.0 +margin_bottom = 42.0 +hint_tooltip = "Lighten/Darken amount" +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +value = 10.0 +align = 1 + +[node name="Slider" type="HSlider" parent="Amount" index="2"] +margin_left = 12.0 +margin_top = 46.0 +margin_right = 104.0 +margin_bottom = 62.0 +rect_min_size = Vector2( 92, 0 ) +hint_tooltip = "Lighten/Darken amount" +focus_mode = 0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +size_flags_vertical = 1 +value = 10.0 +ticks_on_borders = true + +[node name="EmptySpacer" parent="." index="7"] +margin_top = 192.0 +margin_bottom = 204.0 + +[node name="Mirror" parent="." index="8"] +margin_top = 208.0 +margin_bottom = 225.0 +[connection signal="item_selected" from="LightenDarken" to="." method="_on_LightenDarken_item_selected"] +[connection signal="value_changed" from="Amount/Spinbox" to="." method="_on_LightenDarken_value_changed"] +[connection signal="value_changed" from="Amount/Slider" to="." method="_on_LightenDarken_value_changed"] diff --git a/src/Tools/Pencil.gd b/src/Tools/Pencil.gd new file mode 100644 index 000000000..4b0cde1a0 --- /dev/null +++ b/src/Tools/Pencil.gd @@ -0,0 +1,68 @@ +extends "res://src/Tools/Draw.gd" + + +var _last_position := Vector2.INF +var _changed := false + + +class AlphaBlendOp extends Drawer.ColorOp: + var changed := false + + + func process(src: Color, dst: Color) -> Color: + changed = true + src.a *= strength + return dst.blend(src) + + +func _init() -> void: + _drawer.color_op = AlphaBlendOp.new() + + +func draw_start(position : Vector2) -> void: + update_mask() + _changed = false + _drawer.color_op.changed = false + + prepare_undo() + _drawer.reset() + + _draw_line = Tools.shift + if _draw_line: + _line_start = position + _line_end = position + update_line_polylines(_line_start, _line_end) + else: + draw_tool(position) + _last_position = position + Global.canvas.sprite_changed_this_frame = true + cursor_text = "" + + +func draw_move(position : Vector2) -> void: + if _draw_line: + var d = _line_angle_constraint(_line_start, position) + _line_end = d.position + cursor_text = d.text + update_line_polylines(_line_start, _line_end) + else: + draw_fill_gap(_last_position, position) + _last_position = position + cursor_text = "" + Global.canvas.sprite_changed_this_frame = true + + +func draw_end(_position : Vector2) -> void: + if _draw_line: + draw_tool(_line_start) + draw_fill_gap(_line_start, _line_end) + _draw_line = false + if _changed or _drawer.color_op.changed: + commit_undo("Draw") + cursor_text = "" + update_random_image() + + +func _draw_brush_image(image : Image, src_rect: Rect2, dst: Vector2) -> void: + _changed = true + _get_draw_image().blend_rect(image, src_rect, dst) diff --git a/src/Tools/Pencil.tscn b/src/Tools/Pencil.tscn new file mode 100644 index 000000000..8a1c113b6 --- /dev/null +++ b/src/Tools/Pencil.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/Draw.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/Pencil.gd" type="Script" id=3] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 3 ) diff --git a/src/Tools/RectSelect.gd b/src/Tools/RectSelect.gd new file mode 100644 index 000000000..1543faa77 --- /dev/null +++ b/src/Tools/RectSelect.gd @@ -0,0 +1,59 @@ +extends "res://src/Tools/Base.gd" + + +var _start := Rect2(0, 0, 0, 0) +var _offset := Vector2.ZERO +var _drag := false +var _move := false + + +func draw_start(position : Vector2) -> void: + if Global.selection_rectangle.has_point(position): + _move = true + _offset = position + Global.selection_rectangle.move_start(Tools.shift) + _set_cursor_text(Global.selection_rectangle.get_rect()) + 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) + _offset = position + _set_cursor_text(Global.selection_rectangle.get_rect()) + 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) + + +func draw_end(_position : Vector2) -> void: + if _move: + Global.selection_rectangle.move_end() + else: + Global.selection_rectangle.select_rect() + _drag = false + _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 + + +func _set_cursor_text(rect : Rect2) -> void: + cursor_text = "%s, %s" % [rect.position.x, rect.position.y] + cursor_text += " -> %s, %s" % [rect.end.x - 1, rect.end.y - 1] + cursor_text += " (%s, %s)" % [rect.size.x, rect.size.y] diff --git a/src/Tools/RectSelect.tscn b/src/Tools/RectSelect.tscn new file mode 100644 index 000000000..d557baaaf --- /dev/null +++ b/src/Tools/RectSelect.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/RectSelect.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="PixelPerfect" parent="." index="1"] +visible = false + +[node name="EmptySpacer" parent="." index="2"] +visible = false +margin_top = 18.0 +margin_bottom = 30.0 + +[node name="Mirror" parent="." index="3"] +visible = false +margin_top = 18.0 +margin_bottom = 35.0 diff --git a/src/Tools/Zoom.gd b/src/Tools/Zoom.gd new file mode 100644 index 000000000..01f6a15d3 --- /dev/null +++ b/src/Tools/Zoom.gd @@ -0,0 +1,48 @@ +extends "res://src/Tools/Base.gd" + + +var _zoom_mode := 0 + + +func _on_ModeOptions_item_selected(id): + _zoom_mode = id + update_config() + save_config() + + +func _on_FitToFrame_pressed(): + Global.camera.fit_to_frame(Global.current_project.size) + + +func _on_100_pressed(): + Global.camera.zoom = Vector2.ONE + Global.camera.offset = Global.current_project.size / 2 + Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %" + Global.horizontal_ruler.update() + Global.vertical_ruler.update() + + +func get_config() -> Dictionary: + return { + "zoom_mode" : _zoom_mode, + } + + +func set_config(config : Dictionary) -> void: + _zoom_mode = config.get("zoom_mode", _zoom_mode) + + +func update_config() -> void: + $ModeOptions.selected = _zoom_mode + + +func draw_start(_position : Vector2) -> void: + Global.camera.zoom_camera(_zoom_mode * 2 - 1) + + +func draw_move(_position : Vector2) -> void: + pass + + +func draw_end(_position : Vector2) -> void: + pass diff --git a/src/Tools/Zoom.tscn b/src/Tools/Zoom.tscn new file mode 100644 index 000000000..91bfbba8a --- /dev/null +++ b/src/Tools/Zoom.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://src/Tools/Base.tscn" type="PackedScene" id=1] +[ext_resource path="res://src/Tools/Zoom.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="Mode" type="Label" parent="." index="1"] +margin_left = 38.0 +margin_top = 18.0 +margin_right = 78.0 +margin_bottom = 32.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Mode:" + +[node name="ModeOptions" type="OptionButton" parent="." index="2"] +margin_left = 12.0 +margin_top = 36.0 +margin_right = 104.0 +margin_bottom = 56.0 +rect_min_size = Vector2( 92, 0 ) +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Zoom in" +items = [ "Zoom in", null, false, 0, null, "Zoom out", null, false, 1, null ] +selected = 0 + +[node name="Options" type="Label" parent="." index="3"] +margin_left = 30.0 +margin_top = 60.0 +margin_right = 85.0 +margin_bottom = 74.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Options:" + +[node name="FitToFrame" type="Button" parent="." index="4"] +margin_left = 12.0 +margin_top = 78.0 +margin_right = 104.0 +margin_bottom = 98.0 +rect_min_size = Vector2( 92, 0 ) +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "Fit to frame" + +[node name="100%" type="Button" parent="." index="5"] +margin_left = 12.0 +margin_top = 102.0 +margin_right = 104.0 +margin_bottom = 122.0 +rect_min_size = Vector2( 92, 0 ) +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +text = "100% Zoom" + +[node name="PixelPerfect" parent="." index="6"] +visible = false +margin_top = 126.0 +margin_bottom = 150.0 + +[node name="EmptySpacer" parent="." index="7"] +visible = false +margin_top = 126.0 +margin_bottom = 138.0 + +[node name="Mirror" parent="." index="8"] +visible = false +margin_top = 126.0 +margin_bottom = 143.0 +[connection signal="item_selected" from="ModeOptions" to="." method="_on_ModeOptions_item_selected"] +[connection signal="pressed" from="FitToFrame" to="." method="_on_FitToFrame_pressed"] +[connection signal="pressed" from="100%" to="." method="_on_100_pressed"] diff --git a/src/UI/BrushButton.gd b/src/UI/BrushButton.gd index abfd28a4f..1363c7a7f 100644 --- a/src/UI/BrushButton.gd +++ b/src/UI/BrushButton.gd @@ -1,70 +1,29 @@ extends BaseButton -export var brush_type := 0 # Global.Brush_Types.PIXEL -export var custom_brush_index := -3 -var random_brushes := [] +var brush := Brushes.Brush.new() func _on_BrushButton_pressed() -> void: # Delete the brush on middle mouse press if Input.is_action_just_released("middle_mouse"): _on_DeleteButton_pressed() - return - - # Change brush - Global.current_brush_types[Global.brush_type_window_position] = brush_type - Global.custom_brush_indexes[Global.brush_type_window_position] = custom_brush_index - if brush_type == Global.Brush_Types.FILE or brush_type == Global.Brush_Types.RANDOM_FILE or brush_type == Global.Brush_Types.CUSTOM: - if Global.current_tools[Global.brush_type_window_position] == Global.Tools.PENCIL: - Global.color_interpolation_containers[Global.brush_type_window_position].visible = true else: - Global.color_interpolation_containers[Global.brush_type_window_position].visible = false - - Global.update_custom_brush(Global.brush_type_window_position) - Global.brushes_popup.hide() + Global.brushes_popup.select_brush(brush) func _on_DeleteButton_pressed() -> void: - if brush_type != Global.Brush_Types.CUSTOM: + if brush.type != Brushes.CUSTOM: return - if Global.custom_brush_indexes[0] == custom_brush_index: - Global.custom_brush_indexes[0] = -3 - Global.current_brush_types[0] = Global.Brush_Types.PIXEL - Global.update_custom_brush(0) - if Global.custom_brush_indexes[1] == custom_brush_index: - Global.custom_brush_indexes[1] = -3 - Global.current_brush_types[1] = Global.Brush_Types.PIXEL - Global.update_custom_brush(1) - - Global.current_project.undos += 1 - Global.current_project.undo_redo.create_action("Delete Custom Brush") - for i in range(Global.project_brush_container.get_child_count()): - var bb = Global.project_brush_container.get_child(i) - if Global.custom_brush_indexes[0] == bb.custom_brush_index: - Global.custom_brush_indexes[0] -= 1 - if Global.custom_brush_indexes[1] == bb.custom_brush_index: - Global.custom_brush_indexes[1] -= 1 - - Global.current_project.undo_redo.add_do_property(bb, "custom_brush_index", bb.custom_brush_index - 1) - Global.current_project.undo_redo.add_undo_property(bb, "custom_brush_index", bb.custom_brush_index) - - var custom_brushes: Array = Global.current_project.brushes.duplicate() - custom_brushes.remove(custom_brush_index) - - Global.current_project.undo_redo.add_do_property(Global.current_project, "brushes", custom_brushes) - Global.current_project.undo_redo.add_undo_property(Global.current_project, "brushes", Global.current_project.brushes) - Global.current_project.undo_redo.add_do_method(Global, "redo_custom_brush", self) - Global.current_project.undo_redo.add_undo_method(Global, "undo_custom_brush", self) - Global.current_project.undo_redo.commit_action() + Global.brushes_popup.remove_brush(self) func _on_BrushButton_mouse_entered() -> void: - if brush_type == Global.Brush_Types.CUSTOM: + if brush.type == Brushes.CUSTOM: $DeleteButton.visible = true func _on_BrushButton_mouse_exited() -> void: - if brush_type == Global.Brush_Types.CUSTOM: + if brush.type == Brushes.CUSTOM: $DeleteButton.visible = false diff --git a/src/UI/BrushesPopup.gd b/src/UI/BrushesPopup.gd new file mode 100644 index 000000000..e829fd314 --- /dev/null +++ b/src/UI/BrushesPopup.gd @@ -0,0 +1,132 @@ +extends Popup +class_name Brushes + + +class Brush: + var type : int + var image : Image + var random := [] + var index : int + + +signal brush_selected(brush) +signal brush_removed(brush) +enum {PIXEL, CIRCLE, FILLED_CIRCLE, FILE, RANDOM_FILE, CUSTOM} + +var pixel_image = preload("res://assets/graphics/pixel_image.png") +var circle_image = preload("res://assets/graphics/circle_9x9.png") +var circle_filled_image = preload("res://assets/graphics/circle_filled_9x9.png") + + +func _ready() -> void: + var container = Global.brushes_popup.get_node("TabContainer/File/FileBrushContainer") + var button = create_button(pixel_image) + button.brush.type = PIXEL + button.hint_tooltip = "Pixel brush" + container.add_child(button) + button.brush.index = button.get_index() + + button = create_button(circle_image) + button.brush.type = CIRCLE + button.hint_tooltip = "Circle brush" + container.add_child(button) + button.brush.index = button.get_index() + + button = create_button(circle_filled_image) + button.brush.type = FILLED_CIRCLE + button.hint_tooltip = "Filled circle brush" + container.add_child(button) + button.brush.index = button.get_index() + + +func select_brush(brush : Brush) -> void: + emit_signal("brush_selected", brush) + hide() + + +static func get_default_brush() -> Brush: + var brush = Brush.new() + brush.type = PIXEL + brush.index = 0 + return brush + + +static func create_button(image : Image) -> Node: + var button : BaseButton = load("res://src/UI/BrushButton.tscn").instance() + var tex := ImageTexture.new() + tex.create_from_image(image, 0) + button.get_child(0).texture = tex + button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + return button + + +static func add_file_brush(images : Array, hint := "") -> void: + var button = create_button(images[0]) + button.brush.type = FILE if images.size() == 1 else RANDOM_FILE + button.brush.image = images[0] + button.brush.random = images + button.hint_tooltip = hint + var container = Global.brushes_popup.get_node("TabContainer/File/FileBrushContainer") + container.add_child(button) + button.brush.index = button.get_index() + + +static func add_project_brush(image : Image) -> void: + var button = create_button(image) + button.brush.type = CUSTOM + button.brush.image = image + var container = Global.brushes_popup.get_node("TabContainer/Project/ProjectBrushContainer") + container.add_child(button) + button.brush.index = button.get_index() + + +static func clear_project_brush() -> void: + var container = Global.brushes_popup.get_node("TabContainer/Project/ProjectBrushContainer") + for child in container.get_children(): + child.queue_free() + Global.brushes_popup.emit_signal("brush_removed", child.brush) + + +func get_brush(type : int, index : int) -> Brush: + var container + if type == CUSTOM: + container = Global.brushes_popup.get_node("TabContainer/Project/ProjectBrushContainer") + else: + container = Global.brushes_popup.get_node("TabContainer/File/FileBrushContainer") + var brush = get_default_brush() + if index < container.get_child_count(): + brush = container.get_child(index).brush + return brush + + +func remove_brush(brush_button : Node) -> void: + emit_signal("brush_removed", brush_button.brush) + + var project = Global.current_project + var undo_brushes = project.brushes.duplicate() + project.brushes.erase(brush_button.brush.image) + + project.undos += 1 + project.undo_redo.create_action("Delete Custom Brush") + project.undo_redo.add_do_property(project, "brushes", project.brushes) + project.undo_redo.add_undo_property(project, "brushes", undo_brushes) + project.undo_redo.add_do_method(self, "redo_custom_brush", brush_button) + project.undo_redo.add_undo_method(self, "undo_custom_brush", brush_button) + project.undo_redo.add_undo_reference(brush_button) + project.undo_redo.commit_action() + + +func undo_custom_brush(brush_button : BaseButton = null) -> void: + Global.general_undo() + var action_name : String = Global.current_project.undo_redo.get_current_action_name() + if action_name == "Delete Custom Brush": + $TabContainer/Project/ProjectBrushContainer.add_child(brush_button) + $TabContainer/Project/ProjectBrushContainer.move_child(brush_button, brush_button.brush.index) + brush_button.get_node("DeleteButton").visible = false + + +func redo_custom_brush(brush_button : BaseButton = null) -> void: + Global.general_redo() + var action_name : String = Global.current_project.undo_redo.get_current_action_name() + if action_name == "Delete Custom Brush": + $TabContainer/Project/ProjectBrushContainer.remove_child(brush_button) diff --git a/src/UI/BrushesPopup.tscn b/src/UI/BrushesPopup.tscn index 3b021807c..b96585ea3 100644 --- a/src/UI/BrushesPopup.tscn +++ b/src/UI/BrushesPopup.tscn @@ -1,41 +1,12 @@ -[gd_scene load_steps=6 format=2] +[gd_scene load_steps=2 format=2] -[ext_resource path="res://src/UI/BrushButton.tscn" type="PackedScene" id=2] - -[sub_resource type="Image" id=5] -data = { -"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0 ), -"format": "LumAlpha8", -"height": 9, -"mipmaps": false, -"width": 9 -} - -[sub_resource type="ImageTexture" id=2] -flags = 3 -flags = 3 -image = SubResource( 5 ) -size = Vector2( 9, 9 ) - -[sub_resource type="Image" id=6] -data = { -"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 255, 0, 0, 0, 0, 0, 0 ), -"format": "LumAlpha8", -"height": 9, -"mipmaps": false, -"width": 9 -} - -[sub_resource type="ImageTexture" id=4] -flags = 3 -flags = 3 -image = SubResource( 6 ) -size = Vector2( 9, 9 ) +[ext_resource path="res://src/UI/BrushesPopup.gd" type="Script" id=1] [node name="BrushesPopup" type="Popup"] margin_right = 226.0 margin_bottom = 144.0 rect_min_size = Vector2( 0, 144 ) +script = ExtResource( 1 ) [node name="TabContainer" type="TabContainer" parent="."] anchor_right = 1.0 @@ -55,36 +26,8 @@ size_flags_horizontal = 3 scroll_horizontal_enabled = false [node name="FileBrushContainer" type="GridContainer" parent="TabContainer/File"] -margin_right = 104.0 -margin_bottom = 32.0 columns = 6 -[node name="PixelBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )] -hint_tooltip = "Pixel brush" -mouse_default_cursor_shape = 2 - -[node name="CircleBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )] -margin_left = 36.0 -margin_right = 68.0 -hint_tooltip = "Filled circle brush" -mouse_default_cursor_shape = 2 -brush_type = 1 -custom_brush_index = -2 - -[node name="BrushTexture" parent="TabContainer/File/FileBrushContainer/CircleBrushButton" index="0"] -texture = SubResource( 2 ) - -[node name="FilledCircleBrushButton" parent="TabContainer/File/FileBrushContainer" instance=ExtResource( 2 )] -margin_left = 72.0 -margin_right = 104.0 -hint_tooltip = "Circle brush" -mouse_default_cursor_shape = 2 -brush_type = 2 -custom_brush_index = -1 - -[node name="BrushTexture" parent="TabContainer/File/FileBrushContainer/FilledCircleBrushButton" index="0"] -texture = SubResource( 4 ) - [node name="Project" type="ScrollContainer" parent="TabContainer"] visible = false anchor_right = 1.0 @@ -99,7 +42,3 @@ scroll_horizontal_enabled = false [node name="ProjectBrushContainer" type="GridContainer" parent="TabContainer/Project"] columns = 5 - -[editable path="TabContainer/File/FileBrushContainer/CircleBrushButton"] - -[editable path="TabContainer/File/FileBrushContainer/FilledCircleBrushButton"] diff --git a/src/UI/ColorAndToolOptions.gd b/src/UI/ColorAndToolOptions.gd index 8164d9337..44d112fb8 100644 --- a/src/UI/ColorAndToolOptions.gd +++ b/src/UI/ColorAndToolOptions.gd @@ -1,25 +1,23 @@ extends VBoxContainer -var previous_colors := [Color.black, Color.white] +onready var left_picker := $ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/LeftColorPickerButton +onready var right_picker := $ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/RightColorPickerButton + + +func _ready() -> void: + Tools.connect("color_changed", self, "update_color") + left_picker.get_picker().presets_visible = false + right_picker.get_picker().presets_visible = false func _on_ColorSwitch_pressed() -> void: - var temp : Color = Global.color_pickers[0].color - Global.color_pickers[0].color = Global.color_pickers[1].color - Global.color_pickers[1].color = temp - Global.update_custom_brush(0) - Global.update_custom_brush(1) + Tools.swap_color() func _on_ColorPickerButton_color_changed(color : Color, right : bool): - var mouse_button := int(right) - # If the color changed while it's on full transparency, make it opaque (GH issue #54) - if color.a == 0: - if previous_colors[mouse_button].r != color.r or previous_colors[mouse_button].g != color.g or previous_colors[mouse_button].b != color.b: - Global.color_pickers[mouse_button].color.a = 1 - Global.update_custom_brush(mouse_button) - previous_colors[mouse_button] = color + var button := BUTTON_RIGHT if right else BUTTON_LEFT + Tools.assign_color(color, button) func _on_ColorPickerButton_pressed() -> void: @@ -31,108 +29,11 @@ func _on_ColorPickerButton_popup_closed() -> void: func _on_ColorDefaults_pressed() -> void: - Global.color_pickers[0].color = Color.black - Global.color_pickers[1].color = Color.white - Global.update_custom_brush(0) - Global.update_custom_brush(1) + Tools.default_color() -func _on_FitToFrameButton_pressed() -> void: - Global.camera.fit_to_frame(Global.current_project.size) - - -func _on_100ZoomButton_pressed() -> void: - Global.camera.zoom = Vector2.ONE - Global.camera.offset = Global.current_project.size / 2 - Global.zoom_level_label.text = str(round(100 / Global.camera.zoom.x)) + " %" - Global.horizontal_ruler.update() - Global.vertical_ruler.update() - - -func _on_BrushTypeButton_pressed(right : bool) -> void: - var mouse_button := int(right) - Global.brushes_popup.popup(Rect2(Global.brush_type_buttons[mouse_button].rect_global_position, Vector2(226, 72))) - Global.brush_type_window_position = mouse_button - - -func _on_BrushSizeEdit_value_changed(value : float, right : bool) -> void: - var mouse_button := int(right) - var new_size = int(value) - Global.brush_size_edits[mouse_button].value = value - Global.brush_size_sliders[mouse_button].value = value - Global.brush_sizes[mouse_button] = new_size - Global.update_custom_brush(mouse_button) - - -func _on_PixelPerfectMode_toggled(button_pressed : bool, right : bool) -> void: - var mouse_button := int(right) - Global.pixel_perfect[mouse_button] = button_pressed - - -func _on_InterpolateFactor_value_changed(value : float, right : bool) -> void: - var mouse_button := int(right) - Global.interpolate_spinboxes[mouse_button].value = value - Global.interpolate_sliders[mouse_button].value = value - Global.update_custom_brush(mouse_button) - - -func _on_FillAreaOptions_item_selected(ID : int, right : bool) -> void: - var mouse_button := int(right) - Global.fill_areas[mouse_button] = ID - - -func _on_FillWithOptions_item_selected(ID : int, right : bool) -> void: - var mouse_button := int(right) - Global.fill_with[mouse_button] = ID - if ID == 1: - Global.fill_pattern_containers[mouse_button].visible = true +func update_color(color : Color, button : int) -> void: + if button == BUTTON_LEFT: + left_picker.color = color else: - Global.fill_pattern_containers[mouse_button].visible = false - - -func _on_PatternTypeButton_pressed(right : bool) -> void: - var mouse_button := int(right) - Global.pattern_window_position = mouse_button - Global.patterns_popup.popup(Rect2(Global.brush_type_buttons[mouse_button].rect_global_position, Vector2(226, 72))) - - -func _on_PatternOffsetX_value_changed(value : float, right : bool) -> void: - var mouse_button := int(right) - Global.fill_pattern_offsets[mouse_button].x = value - - -func _on_PatternOffsetY_value_changed(value : float, right : bool) -> void: - var mouse_button := int(right) - Global.fill_pattern_offsets[mouse_button].y = value - - -func _on_LightenDarken_item_selected(ID : int, right : bool) -> void: - var mouse_button := int(right) - Global.ld_modes[mouse_button] = ID - - -func _on_LDAmount_value_changed(value : float, right : bool) -> void: - var mouse_button := int(right) - Global.ld_amounts[mouse_button] = value / 100 - Global.ld_amount_sliders[mouse_button].value = value - Global.ld_amount_spinboxes[mouse_button].value = value - - -func _on_ForColorOptions_item_selected(ID : int, right : bool) -> void: - var mouse_button := int(right) - Global.color_picker_for[mouse_button] = ID - - -func _on_ZoomModeOptions_item_selected(ID : int, right : bool) -> void: - var mouse_button := int(right) - Global.zoom_modes[mouse_button] = ID - - -func _on_HorizontalMirroring_toggled(button_pressed : bool, right : bool) -> void: - var mouse_button := int(right) - Global.horizontal_mirror[mouse_button] = button_pressed - - -func _on_VerticalMirroring_toggled(button_pressed : bool, right : bool) -> void: - var mouse_button := int(right) - Global.vertical_mirror[mouse_button] = button_pressed + right_picker.color = color diff --git a/src/UI/ColorAndToolOptions.tscn b/src/UI/ColorAndToolOptions.tscn index 062b2e8b5..00b8121c5 100644 --- a/src/UI/ColorAndToolOptions.tscn +++ b/src/UI/ColorAndToolOptions.tscn @@ -1,42 +1,11 @@ -[gd_scene load_steps=14 format=2] +[gd_scene load_steps=5 format=2] [ext_resource path="res://assets/graphics/dark_themes/misc/color_switch.png" type="Texture" id=1] [ext_resource path="res://assets/graphics/dark_themes/misc/color_defaults.png" type="Texture" id=2] -[ext_resource path="res://assets/graphics/brush_button.png" type="Texture" id=3] -[ext_resource path="res://assets/graphics/dark_themes/tools/horizontal_mirror_on.png" type="Texture" id=4] -[ext_resource path="res://assets/graphics/dark_themes/tools/horizontal_mirror_off.png" type="Texture" id=5] -[ext_resource path="res://assets/graphics/dark_themes/tools/vertical_mirror_on.png" type="Texture" id=6] -[ext_resource path="res://assets/graphics/dark_themes/tools/vertical_mirror_off.png" type="Texture" id=7] [ext_resource path="res://src/UI/ColorAndToolOptions.gd" type="Script" id=8] [sub_resource type="ShortCut" id=1] -[sub_resource type="Image" id=6] -data = { -"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), -"format": "RGBA8", -"height": 32, -"mipmaps": false, -"width": 32 -} - -[sub_resource type="ImageTexture" id=3] -image = SubResource( 6 ) -size = Vector2( 32, 32 ) - -[sub_resource type="Image" id=7] -data = { -"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ), -"format": "RGBA8", -"height": 32, -"mipmaps": false, -"width": 32 -} - -[sub_resource type="ImageTexture" id=5] -image = SubResource( 7 ) -size = Vector2( 32, 32 ) - [node name="ColorAndToolOptions" type="VBoxContainer"] margin_right = 330.0 margin_bottom = 255.0 @@ -129,820 +98,18 @@ size_flags_vertical = 3 [node name="LeftPanelContainer" type="PanelContainer" parent="ScrollContainer/ToolOptions"] margin_left = 16.0 margin_right = 146.0 -margin_bottom = 149.0 +margin_bottom = 14.0 rect_min_size = Vector2( 130, 0 ) size_flags_horizontal = 6 size_flags_vertical = 0 -[node name="LeftToolOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer"] -margin_left = 7.0 -margin_top = 7.0 -margin_right = 123.0 -margin_bottom = 142.0 -size_flags_horizontal = 3 - -[node name="LeftLabel" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -margin_right = 116.0 -margin_bottom = 14.0 -text = "Left tool" -align = 1 -autowrap = true - -[node name="LeftBrushType" type="HBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -margin_top = 18.0 -margin_right = 116.0 -margin_bottom = 50.0 -alignment = 1 - -[node name="LeftBrushTypeButton" type="TextureButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushType"] -margin_left = 1.0 -margin_right = 37.0 -margin_bottom = 32.0 -rect_min_size = Vector2( 36, 32 ) -hint_tooltip = "Select a brush" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 0 -texture_normal = ExtResource( 3 ) - -[node name="BrushTexture" type="TextureRect" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushType/LeftBrushTypeButton"] -margin_right = 32.0 -margin_bottom = 32.0 -texture = SubResource( 3 ) -expand = true -stretch_mode = 6 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="LeftBrushSizeEdit" type="SpinBox" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushType"] -margin_left = 41.0 -margin_right = 115.0 -margin_bottom = 32.0 -mouse_default_cursor_shape = 2 -min_value = 1.0 -value = 1.0 -align = 1 -suffix = "px" - -[node name="LeftBrushSizeSlider" type="HSlider" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -margin_left = 12.0 -margin_top = 54.0 -margin_right = 104.0 -margin_bottom = 70.0 -rect_min_size = Vector2( 92, 0 ) -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -size_flags_vertical = 1 -min_value = 1.0 -value = 1.0 -allow_greater = true -ticks_on_borders = true - -[node name="LeftBrushPixelPerfectMode" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -margin_top = 74.0 -margin_right = 116.0 -margin_bottom = 98.0 -alignment = 1 - -[node name="LeftPixelPerfectMode" type="CheckBox" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushPixelPerfectMode"] -margin_left = 4.0 -margin_right = 112.0 -margin_bottom = 24.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Pixel Perfect" -align = 1 - -[node name="LeftColorInterpolation" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -visible = false -margin_top = 102.0 -margin_right = 116.0 -margin_bottom = 164.0 -alignment = 1 - -[node name="ColorComesFrom" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorInterpolation"] -margin_left = 4.0 -margin_right = 111.0 -margin_bottom = 14.0 -hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" -mouse_filter = 1 -size_flags_horizontal = 4 -text = "Brush color from" - -[node name="LeftInterpolateFactor" type="SpinBox" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorInterpolation"] -margin_left = 21.0 -margin_top = 18.0 -margin_right = 95.0 -margin_bottom = 42.0 -hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -value = 100.0 -align = 1 - -[node name="LeftInterpolateSlider" type="HSlider" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorInterpolation"] -margin_left = 12.0 -margin_top = 46.0 -margin_right = 104.0 -margin_bottom = 62.0 -rect_min_size = Vector2( 92, 0 ) -hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -size_flags_vertical = 1 -value = 100.0 -ticks_on_borders = true - -[node name="LeftFillArea" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -visible = false -margin_top = 71.0 -margin_right = 154.0 -margin_bottom = 252.0 - -[node name="FillAreaLabel" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea"] -margin_left = 54.0 -margin_right = 100.0 -margin_bottom = 15.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Fill area:" - -[node name="LeftFillAreaOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea"] -margin_left = 21.0 -margin_top = 19.0 -margin_right = 133.0 -margin_bottom = 40.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Same color area" -items = [ "Same color area", null, false, 0, null, "Same color pixels", null, false, 1, null ] -selected = 0 - -[node name="FillWithLabel" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea"] -margin_left = 54.0 -margin_top = 44.0 -margin_right = 99.0 -margin_bottom = 59.0 -size_flags_horizontal = 4 -text = "Fill with:" - -[node name="LeftFillWithOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea"] -margin_left = 26.0 -margin_top = 63.0 -margin_right = 127.0 -margin_bottom = 84.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Selected Color" -items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ] -selected = 0 - -[node name="LeftFillPattern" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea"] -visible = false -margin_left = 44.0 -margin_top = 88.0 -margin_right = 109.0 -margin_bottom = 181.0 -size_flags_horizontal = 4 - -[node name="LeftPatternTypeButton" type="TextureButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern"] -margin_left = 16.0 -margin_right = 48.0 -margin_bottom = 32.0 -hint_tooltip = "Select a brush" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -texture_normal = ExtResource( 3 ) - -[node name="PatternTexture" type="TextureRect" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/LeftPatternTypeButton"] -margin_right = 32.0 -margin_bottom = 32.0 -expand = true -stretch_mode = 6 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Offset" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern"] -margin_top = 36.0 -margin_right = 65.0 -margin_bottom = 51.0 -text = "Offset" - -[node name="XOffset" type="HBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern"] -margin_top = 55.0 -margin_right = 65.0 -margin_bottom = 72.0 - -[node name="Label" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/XOffset"] -margin_top = 1.0 -margin_right = 8.0 -margin_bottom = 16.0 -text = "X" - -[node name="LeftPatternOffsetX" type="SpinBox" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/XOffset"] -margin_left = 11.0 -margin_right = 65.0 -margin_bottom = 17.0 -mouse_default_cursor_shape = 2 - -[node name="YOffset" type="HBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern"] -margin_top = 76.0 -margin_right = 65.0 -margin_bottom = 93.0 - -[node name="Label" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/YOffset"] -margin_top = 1.0 -margin_right = 7.0 -margin_bottom = 16.0 -text = "Y" - -[node name="LeftPatternOffsetY" type="SpinBox" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/YOffset"] -margin_left = 10.0 -margin_right = 64.0 -margin_bottom = 17.0 -mouse_default_cursor_shape = 2 - -[node name="LeftLDOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 153.0 -margin_bottom = 158.0 -alignment = 1 - -[node name="LeftLightenDarken" type="OptionButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions"] -margin_left = 30.0 -margin_right = 122.0 -margin_bottom = 23.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Lighten" -items = [ "Lighten", null, false, 0, null, "Darken", null, false, 1, null ] -selected = 0 - -[node name="LDAmountLabel" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions"] -margin_left = 53.0 -margin_top = 27.0 -margin_right = 100.0 -margin_bottom = 42.0 -size_flags_horizontal = 4 -text = "Amount:" - -[node name="LeftLDAmountSpinbox" type="SpinBox" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions"] -margin_left = 49.0 -margin_top = 46.0 -margin_right = 103.0 -margin_bottom = 63.0 -hint_tooltip = "Lighten/Darken amount" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -value = 10.0 -align = 1 - -[node name="LeftLDAmountSlider" type="HSlider" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions"] -margin_left = 30.0 -margin_top = 67.0 -margin_right = 122.0 -margin_bottom = 83.0 -rect_min_size = Vector2( 92, 0 ) -hint_tooltip = "Lighten/Darken amount" -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -size_flags_vertical = 1 -value = 10.0 -ticks_on_borders = true - -[node name="LeftColorPickerOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 159.0 -margin_bottom = 117.0 - -[node name="Label" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorPickerOptions"] -margin_left = 57.0 -margin_right = 101.0 -margin_bottom = 15.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Pick for:" - -[node name="LeftForColorOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorPickerOptions"] -margin_left = 42.0 -margin_top = 19.0 -margin_right = 116.0 -margin_bottom = 42.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Left Color" -items = [ "Left Color", null, false, 0, null, "Right Color", null, false, 1, null ] -selected = 0 - -[node name="LeftZoomOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 153.0 -margin_bottom = 190.0 - -[node name="ModeLabel" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions"] -margin_left = 60.0 -margin_right = 93.0 -margin_bottom = 15.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Mode:" - -[node name="LeftZoomModeOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions"] -margin_left = 30.0 -margin_top = 19.0 -margin_right = 122.0 -margin_bottom = 42.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Zoom in" -items = [ "Zoom in", null, false, 0, null, "Zoom out", null, false, 1, null ] -selected = 0 - -[node name="OptionsLabel" type="Label" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions"] -margin_left = 54.0 -margin_top = 46.0 -margin_right = 99.0 -margin_bottom = 61.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Options:" - -[node name="FitToFrameButton" type="Button" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions"] -margin_left = 30.0 -margin_top = 65.0 -margin_right = 122.0 -margin_bottom = 88.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Fit to frame" - -[node name="100%ZoomButton" type="Button" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions"] -margin_left = 30.0 -margin_top = 92.0 -margin_right = 122.0 -margin_bottom = 115.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "100% Zoom" - -[node name="EmptySpacer" type="Control" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -margin_top = 102.0 -margin_right = 116.0 -margin_bottom = 114.0 -rect_min_size = Vector2( 0, 12 ) - -[node name="LeftMirrorButtons" type="HBoxContainer" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions"] -margin_top = 118.0 -margin_right = 116.0 -margin_bottom = 135.0 -custom_constants/separation = 44 -alignment = 1 - -[node name="LeftHorizontalMirroring" type="TextureButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftMirrorButtons" groups=[ -"UIButtons", -]] -margin_left = 20.0 -margin_right = 35.0 -margin_bottom = 17.0 -hint_tooltip = "Enable horizontal mirrored drawing" -mouse_default_cursor_shape = 2 -toggle_mode = true -texture_normal = ExtResource( 5 ) -texture_pressed = ExtResource( 4 ) - -[node name="LeftVerticalMirroring" type="TextureButton" parent="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftMirrorButtons" groups=[ -"UIButtons", -]] -margin_left = 79.0 -margin_right = 96.0 -margin_bottom = 17.0 -hint_tooltip = "Enable vertical mirrored drawing" -mouse_default_cursor_shape = 2 -toggle_mode = true -texture_normal = ExtResource( 7 ) -texture_pressed = ExtResource( 6 ) - [node name="RightPanelContainer" type="PanelContainer" parent="ScrollContainer/ToolOptions"] margin_left = 183.0 margin_right = 313.0 -margin_bottom = 149.0 +margin_bottom = 14.0 rect_min_size = Vector2( 130, 0 ) size_flags_horizontal = 6 size_flags_vertical = 0 - -[node name="RightToolOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer"] -margin_left = 7.0 -margin_top = 7.0 -margin_right = 123.0 -margin_bottom = 142.0 -size_flags_horizontal = 3 - -[node name="RightLabel" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -margin_right = 116.0 -margin_bottom = 14.0 -text = "Right tool" -align = 1 -autowrap = true - -[node name="RightBrushType" type="HBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -margin_top = 18.0 -margin_right = 116.0 -margin_bottom = 50.0 -alignment = 1 - -[node name="RightBrushTypeButton" type="TextureButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushType"] -margin_left = 1.0 -margin_right = 37.0 -margin_bottom = 32.0 -rect_min_size = Vector2( 36, 32 ) -hint_tooltip = "Select a brush" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 0 -texture_normal = ExtResource( 3 ) - -[node name="BrushTexture" type="TextureRect" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushType/RightBrushTypeButton"] -margin_right = 32.0 -margin_bottom = 32.0 -texture = SubResource( 5 ) -expand = true -stretch_mode = 6 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="RightBrushSizeEdit" type="SpinBox" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushType"] -margin_left = 41.0 -margin_right = 115.0 -margin_bottom = 32.0 -mouse_default_cursor_shape = 2 -min_value = 1.0 -value = 1.0 -align = 1 -suffix = "px" - -[node name="RightBrushSizeSlider" type="HSlider" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -margin_left = 12.0 -margin_top = 54.0 -margin_right = 104.0 -margin_bottom = 70.0 -rect_min_size = Vector2( 92, 0 ) -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -size_flags_vertical = 1 -min_value = 1.0 -value = 1.0 -allow_greater = true -ticks_on_borders = true - -[node name="RightBrushPixelPerfectMode" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -margin_top = 74.0 -margin_right = 116.0 -margin_bottom = 98.0 -alignment = 1 - -[node name="RightPixelPerfectMode" type="CheckBox" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushPixelPerfectMode"] -margin_left = 4.0 -margin_right = 112.0 -margin_bottom = 24.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Pixel Perfect" -align = 1 - -[node name="RightColorInterpolation" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 144.0 -margin_bottom = 131.0 -alignment = 1 - -[node name="ColorComesFrom" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorInterpolation"] -margin_left = 27.0 -margin_right = 117.0 -margin_bottom = 15.0 -hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" -mouse_filter = 1 -size_flags_horizontal = 4 -text = "Brush color from" - -[node name="RightInterpolateFactor" type="SpinBox" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorInterpolation"] -margin_left = 45.0 -margin_top = 19.0 -margin_right = 99.0 -margin_bottom = 36.0 -hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -value = 100.0 -align = 1 - -[node name="RightInterpolateSlider" type="HSlider" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorInterpolation"] -margin_left = 25.0 -margin_top = 40.0 -margin_right = 117.0 -margin_bottom = 56.0 -rect_min_size = Vector2( 92, 0 ) -hint_tooltip = "0: Color from the brush itself, 100: the currently selected color" -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -size_flags_vertical = 1 -value = 100.0 -ticks_on_borders = true - -[node name="RightFillArea" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -visible = false -margin_top = 71.0 -margin_right = 160.0 -margin_bottom = 155.0 - -[node name="FillAreaLabel" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea"] -margin_left = 57.0 -margin_right = 103.0 -margin_bottom = 15.0 -size_flags_horizontal = 4 -text = "Fill area:" - -[node name="RightFillAreaOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea"] -margin_left = 24.0 -margin_top = 19.0 -margin_right = 136.0 -margin_bottom = 40.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Same color area" -items = [ "Same color area", null, false, 0, null, "Same color pixels", null, false, 1, null ] -selected = 0 - -[node name="FillWithLabel" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea"] -margin_left = 57.0 -margin_top = 44.0 -margin_right = 102.0 -margin_bottom = 59.0 -size_flags_horizontal = 4 -text = "Fill with:" - -[node name="RightFillWithOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea"] -margin_left = 38.0 -margin_top = 63.0 -margin_right = 122.0 -margin_bottom = 84.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Selected Color" -items = [ "Selected Color", null, false, 0, null, "Pattern", null, false, 1, null ] -selected = 0 - -[node name="RightFillPattern" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea"] -visible = false -margin_left = 59.0 -margin_top = 88.0 -margin_right = 95.0 -margin_bottom = 120.0 -size_flags_horizontal = 4 - -[node name="RightPatternTypeButton" type="TextureButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern"] -margin_right = 36.0 -margin_bottom = 32.0 -rect_min_size = Vector2( 36, 32 ) -hint_tooltip = "Select a brush" -mouse_default_cursor_shape = 2 -texture_normal = ExtResource( 3 ) - -[node name="PatternTexture" type="TextureRect" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/RightPatternTypeButton"] -margin_right = 32.0 -margin_bottom = 32.0 -expand = true -stretch_mode = 6 -__meta__ = { -"_edit_use_anchors_": false -} - -[node name="Offset" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern"] -margin_left = -185.0 -margin_top = 36.0 -margin_right = -120.0 -margin_bottom = 51.0 -text = "Offset" - -[node name="XOffset" type="HBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern"] -margin_left = -185.0 -margin_top = 55.0 -margin_right = -120.0 -margin_bottom = 72.0 - -[node name="Label" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/XOffset"] -margin_top = 1.0 -margin_right = 8.0 -margin_bottom = 16.0 -text = "X" - -[node name="RightPatternOffsetX" type="SpinBox" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/XOffset"] -margin_left = 11.0 -margin_right = 65.0 -margin_bottom = 17.0 -mouse_default_cursor_shape = 2 - -[node name="YOffset" type="HBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern"] -margin_left = -185.0 -margin_top = 76.0 -margin_right = -120.0 -margin_bottom = 93.0 - -[node name="Label" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/YOffset"] -margin_top = 1.0 -margin_right = 7.0 -margin_bottom = 16.0 -text = "Y" - -[node name="RightPatternOffsetY" type="SpinBox" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/YOffset"] -margin_left = 10.0 -margin_right = 64.0 -margin_bottom = 17.0 -mouse_default_cursor_shape = 2 - -[node name="RightLDOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 138.0 -margin_bottom = 158.0 - -[node name="RightLightenDarken" type="OptionButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions"] -margin_left = 22.0 -margin_right = 114.0 -margin_bottom = 23.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Lighten" -items = [ "Lighten", null, false, 0, null, "Darken", null, false, 1, null ] -selected = 0 - -[node name="LDAmountLabel" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions"] -margin_left = 45.0 -margin_top = 27.0 -margin_right = 92.0 -margin_bottom = 42.0 -size_flags_horizontal = 4 -text = "Amount:" - -[node name="RightLDAmountSpinbox" type="SpinBox" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions"] -margin_left = 42.0 -margin_top = 46.0 -margin_right = 96.0 -margin_bottom = 63.0 -hint_tooltip = "Lighten/Darken amount" -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -value = 10.0 -align = 1 - -[node name="RightLDAmountSlider" type="HSlider" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions"] -margin_left = 22.0 -margin_top = 67.0 -margin_right = 114.0 -margin_bottom = 83.0 -rect_min_size = Vector2( 92, 0 ) -hint_tooltip = "Lighten/Darken amount" -focus_mode = 0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -size_flags_vertical = 1 -value = 10.0 -ticks_on_borders = true - -[node name="RightColorPickerOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 144.0 -margin_bottom = 117.0 - -[node name="Label" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorPickerOptions"] -margin_left = 50.0 -margin_right = 94.0 -margin_bottom = 15.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Pick for:" - -[node name="RightForColorOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorPickerOptions"] -margin_left = 31.0 -margin_top = 19.0 -margin_right = 113.0 -margin_bottom = 42.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Right Color" -items = [ "Left Color", null, false, 0, null, "Right Color", null, false, 1, null ] -selected = 1 - -[node name="RightZoomOptions" type="VBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -visible = false -margin_top = 75.0 -margin_right = 138.0 -margin_bottom = 190.0 - -[node name="ModeLabel" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions"] -margin_left = 52.0 -margin_right = 85.0 -margin_bottom = 15.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Mode:" - -[node name="RightZoomModeOptions" type="OptionButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions"] -margin_left = 22.0 -margin_top = 19.0 -margin_right = 114.0 -margin_bottom = 42.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Zoom out" -items = [ "Zoom in", null, false, 0, null, "Zoom out", null, false, 1, null ] -selected = 1 - -[node name="OptionsLabel" type="Label" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions"] -margin_left = 46.0 -margin_top = 46.0 -margin_right = 91.0 -margin_bottom = 61.0 -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Options:" - -[node name="FitToFrameButton" type="Button" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions"] -margin_left = 22.0 -margin_top = 65.0 -margin_right = 114.0 -margin_bottom = 88.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "Fit to frame" - -[node name="100%ZoomButton" type="Button" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions"] -margin_left = 22.0 -margin_top = 92.0 -margin_right = 114.0 -margin_bottom = 115.0 -rect_min_size = Vector2( 92, 0 ) -mouse_default_cursor_shape = 2 -size_flags_horizontal = 4 -text = "100% Zoom" - -[node name="EmptySpacer" type="Control" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -margin_top = 102.0 -margin_right = 116.0 -margin_bottom = 114.0 -rect_min_size = Vector2( 0, 12 ) - -[node name="RightMirrorButtons" type="HBoxContainer" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions"] -margin_top = 118.0 -margin_right = 116.0 -margin_bottom = 135.0 -custom_constants/separation = 44 -alignment = 1 - -[node name="RightHorizontalMirroring" type="TextureButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightMirrorButtons" groups=[ -"UIButtons", -]] -margin_left = 20.0 -margin_right = 35.0 -margin_bottom = 17.0 -hint_tooltip = "Enable horizontal mirrored drawing" -mouse_default_cursor_shape = 2 -toggle_mode = true -texture_normal = ExtResource( 5 ) -texture_pressed = ExtResource( 4 ) - -[node name="RightVerticalMirroring" type="TextureButton" parent="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightMirrorButtons" groups=[ -"UIButtons", -]] -margin_left = 79.0 -margin_right = 96.0 -margin_bottom = 17.0 -hint_tooltip = "Enable vertical mirrored drawing" -mouse_default_cursor_shape = 2 -toggle_mode = true -texture_normal = ExtResource( 7 ) -texture_pressed = ExtResource( 6 ) [connection signal="pressed" from="ColorButtonsVertical/ColorSwitchCenter/ColorSwitch" to="." method="_on_ColorSwitch_pressed"] [connection signal="color_changed" from="ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/LeftColorPickerButton" to="." method="_on_ColorPickerButton_color_changed" binds= [ false ]] [connection signal="popup_closed" from="ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/LeftColorPickerButton" to="." method="_on_ColorPickerButton_popup_closed"] @@ -951,43 +118,3 @@ texture_pressed = ExtResource( 6 ) [connection signal="popup_closed" from="ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/RightColorPickerButton" to="." method="_on_ColorPickerButton_popup_closed"] [connection signal="pressed" from="ColorButtonsVertical/ColorPickersCenter/ColorPickersHorizontal/RightColorPickerButton" to="." method="_on_ColorPickerButton_pressed"] [connection signal="pressed" from="ColorButtonsVertical/ColorDefaultsCenter/ColorDefaults" to="." method="_on_ColorDefaults_pressed"] -[connection signal="pressed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushType/LeftBrushTypeButton" to="." method="_on_BrushTypeButton_pressed" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushType/LeftBrushSizeEdit" to="." method="_on_BrushSizeEdit_value_changed" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushSizeSlider" to="." method="_on_BrushSizeEdit_value_changed" binds= [ false ]] -[connection signal="toggled" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftBrushPixelPerfectMode/LeftPixelPerfectMode" to="." method="_on_PixelPerfectMode_toggled" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorInterpolation/LeftInterpolateFactor" to="." method="_on_InterpolateFactor_value_changed" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorInterpolation/LeftInterpolateSlider" to="." method="_on_InterpolateFactor_value_changed" binds= [ false ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillAreaOptions" to="." method="_on_FillAreaOptions_item_selected" binds= [ false ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillWithOptions" to="." method="_on_FillWithOptions_item_selected" binds= [ false ]] -[connection signal="pressed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/LeftPatternTypeButton" to="." method="_on_PatternTypeButton_pressed" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/XOffset/LeftPatternOffsetX" to="." method="_on_PatternOffsetX_value_changed" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftFillArea/LeftFillPattern/YOffset/LeftPatternOffsetY" to="." method="_on_PatternOffsetY_value_changed" binds= [ false ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions/LeftLightenDarken" to="." method="_on_LightenDarken_item_selected" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions/LeftLDAmountSpinbox" to="." method="_on_LDAmount_value_changed" binds= [ false ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftLDOptions/LeftLDAmountSlider" to="." method="_on_LDAmount_value_changed" binds= [ false ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftColorPickerOptions/LeftForColorOptions" to="." method="_on_ForColorOptions_item_selected" binds= [ false ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions/LeftZoomModeOptions" to="." method="_on_ZoomModeOptions_item_selected" binds= [ false ]] -[connection signal="pressed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions/FitToFrameButton" to="." method="_on_FitToFrameButton_pressed"] -[connection signal="pressed" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftZoomOptions/100%ZoomButton" to="." method="_on_100ZoomButton_pressed"] -[connection signal="toggled" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftMirrorButtons/LeftHorizontalMirroring" to="." method="_on_HorizontalMirroring_toggled" binds= [ false ]] -[connection signal="toggled" from="ScrollContainer/ToolOptions/LeftPanelContainer/LeftToolOptions/LeftMirrorButtons/LeftVerticalMirroring" to="." method="_on_VerticalMirroring_toggled" binds= [ false ]] -[connection signal="pressed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushType/RightBrushTypeButton" to="." method="_on_BrushTypeButton_pressed" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushType/RightBrushSizeEdit" to="." method="_on_BrushSizeEdit_value_changed" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushSizeSlider" to="." method="_on_BrushSizeEdit_value_changed" binds= [ true ]] -[connection signal="toggled" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightBrushPixelPerfectMode/RightPixelPerfectMode" to="." method="_on_PixelPerfectMode_toggled" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorInterpolation/RightInterpolateFactor" to="." method="_on_InterpolateFactor_value_changed" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorInterpolation/RightInterpolateSlider" to="." method="_on_InterpolateFactor_value_changed" binds= [ true ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillAreaOptions" to="." method="_on_FillAreaOptions_item_selected" binds= [ true ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillWithOptions" to="." method="_on_FillWithOptions_item_selected" binds= [ true ]] -[connection signal="pressed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/RightPatternTypeButton" to="." method="_on_PatternTypeButton_pressed" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/XOffset/RightPatternOffsetX" to="." method="_on_PatternOffsetX_value_changed" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightFillArea/RightFillPattern/YOffset/RightPatternOffsetY" to="." method="_on_PatternOffsetY_value_changed" binds= [ true ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions/RightLightenDarken" to="." method="_on_LightenDarken_item_selected" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions/RightLDAmountSpinbox" to="." method="_on_LDAmount_value_changed" binds= [ true ]] -[connection signal="value_changed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightLDOptions/RightLDAmountSlider" to="." method="_on_LDAmount_value_changed" binds= [ true ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightColorPickerOptions/RightForColorOptions" to="." method="_on_ForColorOptions_item_selected" binds= [ true ]] -[connection signal="item_selected" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions/RightZoomModeOptions" to="." method="_on_ZoomModeOptions_item_selected" binds= [ true ]] -[connection signal="pressed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions/FitToFrameButton" to="." method="_on_FitToFrameButton_pressed"] -[connection signal="pressed" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightZoomOptions/100%ZoomButton" to="." method="_on_100ZoomButton_pressed"] -[connection signal="toggled" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightMirrorButtons/RightHorizontalMirroring" to="." method="_on_HorizontalMirroring_toggled" binds= [ true ]] -[connection signal="toggled" from="ScrollContainer/ToolOptions/RightPanelContainer/RightToolOptions/RightMirrorButtons/RightVerticalMirroring" to="." method="_on_VerticalMirroring_toggled" binds= [ true ]] diff --git a/src/UI/PatternButton.gd b/src/UI/PatternButton.gd index 1c823e75e..d72d53ce4 100644 --- a/src/UI/PatternButton.gd +++ b/src/UI/PatternButton.gd @@ -1,22 +1,8 @@ extends TextureButton -var image : Image -var image_size : Vector2 -var texture : ImageTexture - - -func _ready() -> void: - if image: - image_size = image.get_size() - texture = ImageTexture.new() - texture.create_from_image(image, 0) +var pattern := Patterns.Pattern.new() func _on_PatternButton_pressed() -> void: - Global.pattern_images[Global.pattern_window_position] = image - Global.fill_pattern_containers[Global.pattern_window_position].get_child(0).get_child(0).texture = texture - Global.fill_pattern_containers[Global.pattern_window_position].get_child(2).get_child(1).max_value = image_size.x - 1 - Global.fill_pattern_containers[Global.pattern_window_position].get_child(3).get_child(1).max_value = image_size.y - 1 - - Global.patterns_popup.hide() + Global.patterns_popup.select_pattern(pattern) diff --git a/src/UI/PatternsPopup.gd b/src/UI/PatternsPopup.gd new file mode 100644 index 000000000..1c7e09bb9 --- /dev/null +++ b/src/UI/PatternsPopup.gd @@ -0,0 +1,45 @@ +extends PopupPanel +class_name Patterns + + +class Pattern: + var image : Image + var index : int + +signal pattern_selected(pattern) + +var default_pattern : Pattern = null + + +func select_pattern(pattern : Pattern) -> void: + emit_signal("pattern_selected", pattern) + hide() + + +static func create_button(image : Image) -> Node: + var button : BaseButton = load("res://src/UI/PatternButton.tscn").instance() + var tex := ImageTexture.new() + tex.create_from_image(image, 0) + button.get_child(0).texture = tex + button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND + return button + + +static func add(image : Image, hint := "") -> void: + var button = create_button(image) + button.pattern.image = image + button.hint_tooltip = hint + var container = Global.patterns_popup.get_node("ScrollContainer/PatternContainer") + container.add_child(button) + button.pattern.index = button.get_index() + + if Global.patterns_popup.default_pattern == null: + Global.patterns_popup.default_pattern = button.pattern + + +func get_pattern(index : int) -> Pattern: + var container = Global.patterns_popup.get_node("ScrollContainer/PatternContainer") + var pattern = default_pattern + if index < container.get_child_count(): + pattern = container.get_child(index).pattern + return pattern diff --git a/src/UI/PatternsPopup.tscn b/src/UI/PatternsPopup.tscn index 4484b2d16..9a215445b 100644 --- a/src/UI/PatternsPopup.tscn +++ b/src/UI/PatternsPopup.tscn @@ -1,8 +1,11 @@ -[gd_scene format=2] +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://src/UI/PatternsPopup.gd" type="Script" id=1] [node name="PatternsPopup" type="PopupPanel"] margin_right = 226.0 margin_bottom = 104.0 +script = ExtResource( 1 ) __meta__ = { "_edit_use_anchors_": false } diff --git a/src/UI/ToolButtons.gd b/src/UI/ToolButtons.gd index c39fa78ed..d840f9866 100644 --- a/src/UI/ToolButtons.gd +++ b/src/UI/ToolButtons.gd @@ -1,98 +1,40 @@ extends VBoxContainer -var tools := [] +# Node, shortcut +onready var tools := [ + [$RectSelect, "rectangle_select"], + [$Zoom, "zoom"], + [$ColorPicker, "colorpicker"], + [$Pencil, "pencil"], + [$Eraser, "eraser"], + [$Bucket, "fill"], + [$LightenDarken, "lightdark"], +] func _ready() -> void: - # Node, left mouse shortcut, right mouse shortcut - tools.append([Global.find_node_by_name(self, "Pencil"), "left_pencil_tool", "right_pencil_tool"]) - tools.append([Global.find_node_by_name(self, "Eraser"), "left_eraser_tool", "right_eraser_tool"]) - tools.append([Global.find_node_by_name(self, "Bucket"), "left_fill_tool", "right_fill_tool"]) - tools.append([Global.find_node_by_name(self, "LightenDarken"), "left_lightdark_tool", "right_lightdark_tool"]) - tools.append([Global.find_node_by_name(self, "RectSelect"), "left_rectangle_select_tool", "right_rectangle_select_tool"]) - tools.append([Global.find_node_by_name(self, "ColorPicker"), "left_colorpicker_tool", "right_colorpicker_tool"]) - tools.append([Global.find_node_by_name(self, "Zoom"), "left_zoom_tool", "right_zoom_tool"]) - for t in tools: t[0].connect("pressed", self, "_on_Tool_pressed", [t[0]]) - Global.update_hint_tooltips() func _input(event : InputEvent) -> void: - if Global.has_focus: - if event.is_action_pressed("undo") or event.is_action_pressed("redo") or event.is_action_pressed("redo_secondary"): + if not Global.has_focus: + return + for action in ["undo", "redo", "redo_secondary"]: + if event.is_action_pressed(action): return - for t in tools: # Handle tool shortcuts - if event.is_action_pressed(t[2]): # Shortcut for right button (with Alt) - _on_Tool_pressed(t[0], false, false) - elif event.is_action_pressed(t[1]): # Shortcut for left button - _on_Tool_pressed(t[0], false, true) + for t in tools: # Handle tool shortcuts + if event.is_action_pressed("right_" + t[1] + "_tool"): # Shortcut for right button (with Alt) + Tools.assign_tool(t[0].name, BUTTON_RIGHT) + elif event.is_action_pressed("left_" + t[1] + "_tool"): # Shortcut for left button + Tools.assign_tool(t[0].name, BUTTON_LEFT) -func _on_Tool_pressed(tool_pressed : BaseButton, mouse_press := true, key_for_left := true) -> void: - var current_action := tool_pressed.name - var current_tool : int = Global.Tools.keys().find(current_action.to_upper()) - var left_tool_name := str(Global.Tools.keys()[Global.current_tools[0]]).to_lower() - var right_tool_name := str(Global.Tools.keys()[Global.current_tools[1]]).to_lower() - var current_mouse_button := -1 - - if (mouse_press and Input.is_action_just_released("left_mouse")) or (!mouse_press and key_for_left): - left_tool_name = current_action.to_lower() - current_mouse_button = Global.Mouse_Button.LEFT - - elif (mouse_press and Input.is_action_just_released("right_mouse")) or (!mouse_press and !key_for_left): - right_tool_name = current_action.to_lower() - current_mouse_button = Global.Mouse_Button.RIGHT - - if current_mouse_button != -1: - Global.current_tools[current_mouse_button] = current_tool - # Start from 1, so the label won't get invisible - for i in range(1, Global.tool_options_containers[current_mouse_button].get_child_count()): - Global.tool_options_containers[current_mouse_button].get_child(i).visible = false - - Global.tool_options_containers[current_mouse_button].get_node("EmptySpacer").visible = true - - # Tool options visible depending on the selected tool - if current_tool == Global.Tools.PENCIL: - Global.brush_type_containers[current_mouse_button].visible = true - Global.brush_size_sliders[current_mouse_button].visible = true - Global.pixel_perfect_containers[current_mouse_button].visible = true - Global.mirror_containers[current_mouse_button].visible = true - if Global.current_brush_types[current_mouse_button] == Global.Brush_Types.FILE or Global.current_brush_types[current_mouse_button] == Global.Brush_Types.CUSTOM or Global.current_brush_types[current_mouse_button] == Global.Brush_Types.RANDOM_FILE: - Global.color_interpolation_containers[current_mouse_button].visible = true - elif current_tool == Global.Tools.ERASER: - Global.brush_type_containers[current_mouse_button].visible = true - Global.brush_size_sliders[current_mouse_button].visible = true - Global.pixel_perfect_containers[current_mouse_button].visible = true - Global.mirror_containers[current_mouse_button].visible = true - elif current_tool == Global.Tools.BUCKET: - Global.fill_area_containers[current_mouse_button].visible = true - Global.mirror_containers[current_mouse_button].visible = true - elif current_tool == Global.Tools.LIGHTENDARKEN: - Global.brush_type_containers[current_mouse_button].visible = true - Global.brush_size_sliders[current_mouse_button].visible = true - Global.pixel_perfect_containers[current_mouse_button].visible = true - Global.ld_containers[current_mouse_button].visible = true - Global.mirror_containers[current_mouse_button].visible = true - elif current_tool == Global.Tools.COLORPICKER: - Global.colorpicker_containers[current_mouse_button].visible = true - elif current_tool == Global.Tools.ZOOM: - Global.zoom_containers[current_mouse_button].visible = true - - for t in tools: - var tool_name : String = t[0].name.to_lower() - var texture_button : TextureRect = t[0].get_child(0) - - if tool_name == left_tool_name and tool_name == right_tool_name: - Global.change_button_texturerect(texture_button, "%s_l_r.png" % tool_name.to_lower()) - elif tool_name == left_tool_name: - Global.change_button_texturerect(texture_button, "%s_l.png" % tool_name.to_lower()) - elif tool_name == right_tool_name: - Global.change_button_texturerect(texture_button, "%s_r.png" % tool_name.to_lower()) - else: - Global.change_button_texturerect(texture_button, "%s.png" % tool_name.to_lower()) - - Global.left_cursor_tool_texture.create_from_image(load("res://assets/graphics/cursor_icons/%s_cursor.png" % left_tool_name), 0) - Global.right_cursor_tool_texture.create_from_image(load("res://assets/graphics/cursor_icons/%s_cursor.png" % right_tool_name), 0) +func _on_Tool_pressed(tool_pressed : BaseButton) -> void: + var button := -1 + button = BUTTON_LEFT if Input.is_action_just_released("left_mouse") else button + button = BUTTON_RIGHT if Input.is_action_just_released("right_mouse") else button + if button != -1: + Tools.assign_tool(tool_pressed.name, button) diff --git a/src/UI/TopMenuContainer.gd b/src/UI/TopMenuContainer.gd index 31004f28e..8279d37c4 100644 --- a/src/UI/TopMenuContainer.gd +++ b/src/UI/TopMenuContainer.gd @@ -39,6 +39,9 @@ func setup_edit_menu() -> void: var edit_menu_items := { "Undo" : InputMap.get_action_list("undo")[0].get_scancode_with_modifiers(), "Redo" : InputMap.get_action_list("redo")[0].get_scancode_with_modifiers(), + "Copy" : InputMap.get_action_list("copy")[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" : 0, "Preferences" : 0 } @@ -201,15 +204,16 @@ func edit_menu_id_pressed(id : int) -> void: Global.control.redone = true Global.current_project.undo_redo.redo() Global.control.redone = false - 2: # Clear selection - Global.canvas.handle_undo("Rectangle Select") - Global.selection_rectangle.polygon[0] = Vector2.ZERO - Global.selection_rectangle.polygon[1] = Vector2.ZERO - Global.selection_rectangle.polygon[2] = Vector2.ZERO - Global.selection_rectangle.polygon[3] = Vector2.ZERO - Global.current_project.selected_pixels.clear() - Global.canvas.handle_redo("Rectangle Select") - 3: # Preferences + 2: # Copy + Global.selection_rectangle.copy() + 3: # paste + Global.selection_rectangle.paste() + 4: # Delete + Global.selection_rectangle.delete() + 5: # Clear selection + Global.selection_rectangle.set_rect(Rect2(0, 0, 0, 0)) + Global.selection_rectangle.select_rect() + 6: # Preferences Global.preferences_dialog.popup_centered(Vector2(400, 280)) Global.dialog_open(true)