extends Node signal color_changed(color, button) var pen_pressure := 1.0 var horizontal_mirror := false var vertical_mirror := false var control := false var shift := false var alt := false var tools := { "RectSelect": Tool.new("RectSelect", "Rectangular Selection", "rectangle_select", "", [], "SelectionTools"), "EllipseSelect": Tool.new("EllipseSelect", "Elliptical Selection", "ellipse_select", "", [], "SelectionTools"), "PolygonSelect": Tool.new( "PolygonSelect", "Polygonal Selection", "polygon_select", "Double-click to connect the last point to the starting point", [], "SelectionTools" ), "ColorSelect": Tool.new("ColorSelect", "Select By Color", "color_select", "", [], "SelectionTools"), "MagicWand": Tool.new("MagicWand", "Magic Wand", "magic_wand", "", [], "SelectionTools"), "Lasso": Tool.new("Lasso", "Lasso / Free Select Tool", "lasso", "", [], "SelectionTools"), "Move": Tool.new("Move", "Move", "move"), "Zoom": Tool.new("Zoom", "Zoom", "zoom"), "Pan": Tool.new("Pan", "Pan", "pan"), "ColorPicker": Tool.new( "ColorPicker", "Color Picker", "colorpicker", "Select a color from a pixel of the sprite" ), "Pencil": Tool.new("Pencil", "Pencil", "pencil", "Hold %s to make a line", ["Shift"]), "Eraser": Tool.new("Eraser", "Eraser", "eraser", "Hold %s to make a line", ["Shift"]), "Bucket": Tool.new("Bucket", "Bucket", "fill"), "Shading": Tool.new("Shading", "Shading Tool", "shading"), "LineTool": Tool.new( "LineTool", "Line Tool", "linetool", """Hold %s to snap the angle of the line Hold %s to center the shape on the click origin Hold %s to displace the shape's origin""", ["Shift", "Ctrl", "Alt"] ), "RectangleTool": Tool.new( "RectangleTool", "Rectangle Tool", "rectangletool", """Hold %s to create a 1:1 shape Hold %s to center the shape on the click origin Hold %s to displace the shape's origin""", ["Shift", "Ctrl", "Alt"] ), "EllipseTool": Tool.new( "EllipseTool", "Ellipse Tool", "ellipsetool", """Hold %s to create a 1:1 shape Hold %s to center the shape on the click origin Hold %s to displace the shape's origin""", ["Shift", "Ctrl", "Alt"] ), } var _tool_button_scene: PackedScene = preload("res://src/Tools/ToolButton.tscn") var _slots := {} var _panels := {} var _tool_buttons: Node var _active_button := -1 var _last_position := Vector2.INF class Tool: var name := "" var display_name := "" var scene: PackedScene var icon: Texture var shortcut := "" var extra_hint := "" var extra_shortcuts := [] # Array of String(s) var button_node: BaseButton func _init( _name: String, _display_name: String, _shortcut: String, _extra_hint := "", _extra_shortucts := [], subdir := "" ) -> void: name = _name display_name = _display_name shortcut = _shortcut extra_hint = _extra_hint extra_shortcuts = _extra_shortucts icon = load("res://assets/graphics/tools/%s.png" % name.to_lower()) if subdir.empty(): scene = load("res://src/Tools/%s.tscn" % name) else: scene = load("res://src/Tools/%s/%s.tscn" % [subdir, name]) func generate_hint_tooltip() -> String: var left_shortcut: String = InputMap.get_action_list("left_" + shortcut + "_tool")[0].as_text() var right_shortcut: String = InputMap.get_action_list("right_" + shortcut + "_tool")[0].as_text() var hint := display_name hint += "\n\n%s for left mouse button\n%s for right mouse button" if !extra_hint.empty(): hint += "\n\n" + extra_hint var shortcuts := [left_shortcut, right_shortcut] shortcuts.append_array(extra_shortcuts) hint = tr(hint) % shortcuts return hint class Slot: var name: String var kname: String var tool_node: Node = null var button: int var color: Color var pixel_perfect := 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, } 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) func _ready() -> void: _tool_buttons = Global.control.find_node("ToolButtons") for t in tools: add_tool_button(tools[t]) _slots[BUTTON_LEFT] = Slot.new("Left tool") _slots[BUTTON_RIGHT] = Slot.new("Right tool") _panels[BUTTON_LEFT] = Global.control.find_node("LeftPanelContainer", true, false) _panels[BUTTON_RIGHT] = Global.control.find_node("RightPanelContainer", true, false) var tool_name: String = Global.config_cache.get_value( _slots[BUTTON_LEFT].kname, "tool", "Pencil" ) if not tool_name in tools: tool_name = "Pencil" set_tool(tool_name, BUTTON_LEFT) tool_name = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "tool", "Eraser") if not tool_name in tools: tool_name = "Eraser" set_tool(tool_name, BUTTON_RIGHT) update_tool_buttons() update_tool_cursors() horizontal_mirror = Global.config_cache.get_value("preferences", "horizontal_mirror", false) vertical_mirror = Global.config_cache.get_value("preferences", "vertical_mirror", false) # Yield is necessary for the color picker nodes to update their color values yield(get_tree(), "idle_frame") var color_value: Color = Global.config_cache.get_value( _slots[BUTTON_LEFT].kname, "color", Color.black ) assign_color(color_value, BUTTON_LEFT, false) color_value = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "color", Color.white) assign_color(color_value, BUTTON_RIGHT, false) func add_tool_button(t: Tool) -> void: var tool_button: BaseButton = _tool_button_scene.instance() tool_button.name = t.name tool_button.get_node("ToolIcon").texture = t.icon t.button_node = tool_button _tool_buttons.add_child(tool_button) tool_button.connect("pressed", _tool_buttons, "_on_Tool_pressed", [tool_button]) func set_tool(name: String, button: int) -> void: var slot = _slots[button] var panel: Node = _panels[button] var node: Node = tools[name].scene.instance() if button == BUTTON_LEFT: # As guides are only moved with left mouse if name == "Pan": # tool you want to give more access at guides Global.move_guides_on_canvas = true else: Global.move_guides_on_canvas = false 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, false) assign_color(left, BUTTON_RIGHT, false) func assign_color(color: Color, button: int, change_alpha := true) -> void: var c: Color = _slots[button].color # This was requested by Issue #54 on GitHub if color.a == 0 and change_alpha: 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 set_button_size(button_size: int) -> void: if button_size == Global.ButtonSize.SMALL: for t in _tool_buttons.get_children(): t.rect_min_size = Vector2(24, 24) t.get_node("BackgroundLeft").rect_size.x = 12 t.get_node("BackgroundRight").rect_size.x = 12 t.get_node("BackgroundRight").rect_position = Vector2(24, 24) else: for t in _tool_buttons.get_children(): t.rect_min_size = Vector2(32, 32) t.get_node("BackgroundLeft").rect_size.x = 16 t.get_node("BackgroundRight").rect_size.x = 16 t.get_node("BackgroundRight").rect_position = Vector2(32, 32) func update_tool_buttons() -> void: for child in _tool_buttons.get_children(): var left_background: NinePatchRect = child.get_node("BackgroundLeft") var right_background: NinePatchRect = child.get_node("BackgroundRight") left_background.visible = _slots[BUTTON_LEFT].tool_node.name == child.name right_background.visible = _slots[BUTTON_RIGHT].tool_node.name == child.name func update_hint_tooltips() -> void: for tool_name in tools: var t: Tool = tools[tool_name] t.button_node.hint_tooltip = t.generate_hint_tooltip() func update_tool_cursors() -> void: var left_image = load( "res://assets/graphics/tools/cursors/%s.png" % _slots[BUTTON_LEFT].tool_node.name.to_lower() ) Global.left_cursor.texture = left_image var right_image = load( ( "res://assets/graphics/tools/cursors/%s.png" % _slots[BUTTON_RIGHT].tool_node.name.to_lower() ) ) Global.right_cursor.texture = right_image 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 draw_preview() -> void: _slots[BUTTON_LEFT].tool_node.draw_preview() _slots[BUTTON_RIGHT].tool_node.draw_preview() func handle_draw(position: Vector2, event: InputEvent) -> void: if not (Global.can_draw and Global.has_focus): return var draw_pos := position if Global.mirror_view: draw_pos.x = Global.current_project.size.x - position.x - 1 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(draw_pos) elif not event.pressed and event.button_index == _active_button: _slots[_active_button].tool_node.draw_end(draw_pos) _active_button = -1 if event is InputEventMouseMotion: pen_pressure = event.pressure if Global.pressure_sensitivity_mode == Global.PressureSensitivity.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(draw_pos) 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