diff --git a/assets/graphics/blue_themes/tools/linetool.png b/assets/graphics/blue_themes/tools/linetool.png new file mode 100644 index 000000000..23f3bf308 Binary files /dev/null and b/assets/graphics/blue_themes/tools/linetool.png differ diff --git a/assets/graphics/blue_themes/tools/linetool.png.import b/assets/graphics/blue_themes/tools/linetool.png.import new file mode 100644 index 000000000..9d386bd30 --- /dev/null +++ b/assets/graphics/blue_themes/tools/linetool.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/linetool.png-4ed7f0aa497e149a9bb715508e7b79d1.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/blue_themes/tools/linetool.png" +dest_files=[ "res://.import/linetool.png-4ed7f0aa497e149a9bb715508e7b79d1.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/assets/graphics/cursor_icons/linetool_cursor.png b/assets/graphics/cursor_icons/linetool_cursor.png new file mode 100644 index 000000000..4ac0af7ef Binary files /dev/null and b/assets/graphics/cursor_icons/linetool_cursor.png differ diff --git a/assets/graphics/cursor_icons/linetool_cursor.png.import b/assets/graphics/cursor_icons/linetool_cursor.png.import new file mode 100644 index 000000000..973b6c9d5 --- /dev/null +++ b/assets/graphics/cursor_icons/linetool_cursor.png.import @@ -0,0 +1,13 @@ +[remap] + +importer="image" +type="Image" +path="res://.import/linetool_cursor.png-38c59a143cf71cc8a7903919aba16145.image" + +[deps] + +source_file="res://assets/graphics/cursor_icons/linetool_cursor.png" +dest_files=[ "res://.import/linetool_cursor.png-38c59a143cf71cc8a7903919aba16145.image" ] + +[params] + diff --git a/assets/graphics/dark_themes/tools/linetool.png b/assets/graphics/dark_themes/tools/linetool.png new file mode 100644 index 000000000..23f3bf308 Binary files /dev/null and b/assets/graphics/dark_themes/tools/linetool.png differ diff --git a/assets/graphics/dark_themes/tools/linetool.png.import b/assets/graphics/dark_themes/tools/linetool.png.import new file mode 100644 index 000000000..7700251e5 --- /dev/null +++ b/assets/graphics/dark_themes/tools/linetool.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/linetool.png-49f0a0966a97070b2c54583188740fb8.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/dark_themes/tools/linetool.png" +dest_files=[ "res://.import/linetool.png-49f0a0966a97070b2c54583188740fb8.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/assets/graphics/light_themes/tools/linetool.png b/assets/graphics/light_themes/tools/linetool.png new file mode 100644 index 000000000..655da8a5f Binary files /dev/null and b/assets/graphics/light_themes/tools/linetool.png differ diff --git a/assets/graphics/light_themes/tools/linetool.png.import b/assets/graphics/light_themes/tools/linetool.png.import new file mode 100644 index 000000000..b24a46978 --- /dev/null +++ b/assets/graphics/light_themes/tools/linetool.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/linetool.png-41d8bda5991aa6c858e29d67515cdd5f.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/graphics/light_themes/tools/linetool.png" +dest_files=[ "res://.import/linetool.png-41d8bda5991aa6c858e29d67515cdd5f.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=false +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/project.godot b/project.godot index a61df0617..537853ae6 100644 --- a/project.godot +++ b/project.godot @@ -556,6 +556,16 @@ alt={ "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777240,"unicode":0,"echo":false,"script":null) ] } +left_linetool_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":76,"unicode":0,"echo":false,"script":null) + ] +} +right_linetool_tool={ +"deadzone": 0.5, +"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":true,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":76,"unicode":0,"echo":false,"script":null) + ] +} [locale] diff --git a/src/Autoload/Global.gd b/src/Autoload/Global.gd index afbb70133..a6a832e4a 100644 --- a/src/Autoload/Global.gd +++ b/src/Autoload/Global.gd @@ -547,6 +547,17 @@ Hold %s to make a line""") % [InputMap.get_action_list("left_eraser_tool")[0].as %s for left mouse button %s for right mouse button""") % [InputMap.get_action_list("left_lightdark_tool")[0].as_text(), InputMap.get_action_list("right_lightdark_tool")[0].as_text()] + var linetool : BaseButton = find_node_by_name(root, "LineTool") + linetool.hint_tooltip = tr("""Line Tool + +%s for left mouse button +%s for right mouse button + +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""") % [InputMap.get_action_list("left_linetool_tool")[0].as_text(), InputMap.get_action_list("right_linetool_tool")[0].as_text(), "Shift", "Ctrl", "Alt"] + + var recttool : BaseButton = find_node_by_name(root, "RectangleTool") recttool.hint_tooltip = tr("""Rectangle Tool diff --git a/src/Autoload/Tools.gd b/src/Autoload/Tools.gd index a9f90be79..96bed9418 100644 --- a/src/Autoload/Tools.gd +++ b/src/Autoload/Tools.gd @@ -50,6 +50,7 @@ var _tools = { "Eraser" : "res://src/Tools/Eraser.tscn", "Bucket" : "res://src/Tools/Bucket.tscn", "LightenDarken" : "res://src/Tools/LightenDarken.tscn", + "LineTool" : "res://src/Tools/LineTool.tscn", "RectangleTool" : "res://src/Tools/RectangleTool.tscn", "EllipseTool" : "res://src/Tools/EllipseTool.tscn", } diff --git a/src/Tools/LineTool.gd b/src/Tools/LineTool.gd new file mode 100644 index 000000000..c6dc00ca2 --- /dev/null +++ b/src/Tools/LineTool.gd @@ -0,0 +1,188 @@ +extends "res://src/Tools/Draw.gd" + + +var _original_pos := Vector2.ZERO +var _start := Vector2.ZERO +var _offset := Vector2.ZERO +var _dest := Vector2.ZERO +var _drawing := false +var _displace_origin := false +var _thickness := 1 + + +func _init() -> void: + _drawer.color_op = Drawer.ColorOp.new() + + +func _on_Thickness_value_changed(value: int) -> void: + _thickness = value + update_config() + save_config() + + +func get_config() -> Dictionary: + var config := .get_config() + config["thickness"] = _thickness + return config + + +func set_config(config: Dictionary) -> void: + .set_config(config) + _thickness = config.get("thickness", _thickness) + + +func update_config() -> void: + .update_config() + $ThicknessSlider.value = _thickness + $ShapeThickness/ThicknessSpinbox.value = _thickness + + +func _get_shape_points(_size: Vector2) -> PoolVector2Array: + return PoolVector2Array() + + +func _get_shape_points_filled(_size: Vector2) -> PoolVector2Array: + return PoolVector2Array() + + +func _input(event : InputEvent) -> void: + if _drawing: + if event.is_action_pressed("alt"): + _displace_origin = true + elif event.is_action_released("alt"): + _displace_origin = false + + +func draw_start(position : Vector2) -> void: + Global.canvas.selection.transform_content_confirm() + update_mask() + + _original_pos = position + _start = position + _offset = position + _dest = position + _drawing = true + + +func draw_move(position : Vector2) -> void: + if _drawing: + if _displace_origin: + _original_pos += position - _offset + var d = _line_angle_constraint(_original_pos, position) + _dest = d.position + if Tools.control: + _start = _original_pos - (_dest - _original_pos) + else: + _start = _original_pos + cursor_text = d.text + _offset = position + + +func draw_end(_position : Vector2) -> void: + if _drawing: + _draw_shape() + + _original_pos = Vector2.ZERO + _start = Vector2.ZERO + _dest = Vector2.ZERO + _drawing = false + _displace_origin = false + cursor_text = "" + + +func draw_preview() -> void: + if _drawing: + var canvas : CanvasItem = Global.canvas.previews + var indicator := BitMap.new() + var start := _start + if _start.x > _dest.x: + start.x = _dest.x + if _start.y > _dest.y: + start.y = _dest.y + + var points := _get_points() + var t_offset := _thickness - 1 + var t_offsetv := Vector2(t_offset, t_offset) + indicator.create((_dest - _start).abs() + t_offsetv * 2 + Vector2.ONE) + + for point in points: + var p : Vector2 = point - start + t_offsetv + indicator.set_bit(p, 1) + + canvas.draw_set_transform(start - t_offsetv, canvas.rotation, canvas.scale) + + for line in _create_polylines(indicator): + canvas.draw_polyline(PoolVector2Array(line), tool_slot.color) + + canvas.draw_set_transform(canvas.position, canvas.rotation, canvas.scale) + + +func _draw_shape() -> void: +# var rect := _get_result_rect(origin, dest) + var points := _get_points() + prepare_undo() + for point in points: + # Reset drawer every time because pixel perfect sometimes breaks the tool + _drawer.reset() + # Draw each point offseted based on the shape's thickness + draw_tool(point) + + commit_undo("Draw Shape") + + +func _get_points() -> PoolVector2Array: + var array := [] + var dx := int(abs(_dest.x - _start.x)) + var dy := int(-abs(_dest.y - _start.y)) + var err := dx + dy + var e2 := err << 1 + var sx = 1 if _start.x < _dest.x else -1 + var sy = 1 if _start.y < _dest.y else -1 + var x = _start.x + var y = _start.y + + var start := _start - Vector2.ONE * (_thickness >> 1) + var end := start + Vector2.ONE * _thickness + for yy in range(start.y, end.y): + for xx in range(start.x, end.x): + array.append(Vector2(xx, yy)) + + while !(x == _dest.x && y == _dest.y): + e2 = err << 1 + if e2 >= dy: + err += dy + x += sx + if e2 <= dx: + err += dx + y += sy + + var pos := Vector2(x, y) + start = pos - Vector2.ONE * (_thickness >> 1) + end = start + Vector2.ONE * _thickness + for yy in range(start.y, end.y): + for xx in range(start.x, end.x): + array.append(Vector2(xx, yy)) + + return PoolVector2Array(array) + + +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.shift: + 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 + angle *= -1 + angle += 360 if angle < 0 else 0 + result.text = str(stepify(angle, 0.01)) + "°" + result.position = end.round() + return result diff --git a/src/Tools/LineTool.tscn b/src/Tools/LineTool.tscn new file mode 100644 index 000000000..a395fc47e --- /dev/null +++ b/src/Tools/LineTool.tscn @@ -0,0 +1,73 @@ +[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/LineTool.gd" type="Script" id=2] + +[node name="ToolOptions" instance=ExtResource( 1 )] +script = ExtResource( 2 ) + +[node name="Brush" parent="." index="1"] +visible = false + +[node name="ShapeThickness" type="HBoxContainer" parent="." index="2"] +margin_left = 6.0 +margin_top = 18.0 +margin_right = 110.0 +margin_bottom = 42.0 +size_flags_horizontal = 4 +custom_constants/separation = 0 + +[node name="Label2" type="Label" parent="ShapeThickness" index="0"] +margin_top = 5.0 +margin_right = 30.0 +margin_bottom = 19.0 +text = "Size:" +align = 1 + +[node name="ThicknessSpinbox" type="SpinBox" parent="ShapeThickness" index="1"] +margin_left = 30.0 +margin_right = 104.0 +margin_bottom = 24.0 +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +min_value = 1.0 +value = 1.0 +align = 1 +suffix = "px" + +[node name="ThicknessSlider" type="HSlider" parent="." index="3"] +margin_left = 12.0 +margin_top = 46.0 +margin_right = 104.0 +margin_bottom = 62.0 +rect_min_size = Vector2( 92, 0 ) +mouse_default_cursor_shape = 2 +size_flags_horizontal = 4 +size_flags_vertical = 1 +min_value = 1.0 +value = 1.0 + +[node name="BrushSize" parent="." index="4"] +visible = false +margin_top = 18.0 +margin_bottom = 34.0 + +[node name="PixelPerfect" parent="." index="5"] +visible = false +margin_top = 66.0 +margin_bottom = 90.0 + +[node name="ColorInterpolation" parent="." index="6"] +margin_top = 66.0 +margin_bottom = 128.0 + +[node name="EmptySpacer" parent="." index="7"] +margin_top = 66.0 +margin_bottom = 78.0 + +[node name="Mirror" parent="." index="8"] +margin_top = 82.0 +margin_bottom = 99.0 + +[connection signal="value_changed" from="ShapeThickness/ThicknessSpinbox" to="." method="_on_Thickness_value_changed"] +[connection signal="value_changed" from="ThicknessSlider" to="." method="_on_Thickness_value_changed"] diff --git a/src/Tools/ShapeDrawer.gd b/src/Tools/ShapeDrawer.gd index 3f93b5a59..e83b9683e 100644 --- a/src/Tools/ShapeDrawer.gd +++ b/src/Tools/ShapeDrawer.gd @@ -92,7 +92,7 @@ func draw_end(position : Vector2) -> void: func draw_preview() -> void: if _drawing: - var canvas = Global.canvas.previews + var canvas : CanvasItem = Global.canvas.previews var indicator := BitMap.new() var rect := _get_result_rect(_start, _dest) var points := _get_points(rect.size) @@ -115,7 +115,7 @@ func _draw_shape(origin: Vector2, dest: Vector2) -> void: var points := _get_points(rect.size) prepare_undo() for point in points: - # Reset drawer every time because pixel perfect sometimes brake the tool + # Reset drawer every time because pixel perfect sometimes breaks the tool _drawer.reset() # Draw each point offseted based on the shape's thickness draw_tool(rect.position + point - Vector2.ONE * (_thickness - 1)) diff --git a/src/UI/ToolButtons.gd b/src/UI/ToolButtons.gd index d49733be8..b3a2c8b79 100644 --- a/src/UI/ToolButtons.gd +++ b/src/UI/ToolButtons.gd @@ -14,6 +14,7 @@ onready var tools := [ [$Eraser, "eraser"], [$Bucket, "fill"], [$LightenDarken, "lightdark"], + [$LineTool, "linetool"], [$RectangleTool, "rectangletool"], [$EllipseTool, "ellipsetool"], ] diff --git a/src/UI/UI.tscn b/src/UI/UI.tscn index d47877c28..791f4c673 100644 --- a/src/UI/UI.tscn +++ b/src/UI/UI.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=33 format=2] +[gd_scene load_steps=34 format=2] [ext_resource path="res://src/UI/ToolButtons.gd" type="Script" id=1] [ext_resource path="res://src/UI/Canvas/CanvasPreview.tscn" type="PackedScene" id=2] @@ -29,6 +29,7 @@ [ext_resource path="res://assets/graphics/tool_backgrounds/r.png" type="Texture" id=27] [ext_resource path="res://assets/graphics/dark_themes/tools/colorselect.png" type="Texture" id=28] [ext_resource path="res://assets/graphics/dark_themes/tools/magicwand.png" type="Texture" id=29] +[ext_resource path="res://assets/graphics/dark_themes/tools/linetool.png" type="Texture" id=30] [sub_resource type="ShaderMaterial" id=1] shader = ExtResource( 9 ) @@ -92,7 +93,7 @@ __meta__ = { margin_left = 7.0 margin_top = 7.0 margin_right = 39.0 -margin_bottom = 471.0 +margin_bottom = 507.0 size_flags_horizontal = 4 size_flags_vertical = 0 script = ExtResource( 1 ) @@ -376,7 +377,7 @@ __meta__ = { "_edit_use_anchors_": false } -[node name="RectangleTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +[node name="LineTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] margin_top = 396.0 @@ -386,6 +387,31 @@ rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3 +[node name="Background" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/LineTool"] +margin_right = 32.0 +margin_bottom = 32.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ToolIcon" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/LineTool"] +margin_right = 32.0 +margin_bottom = 32.0 +texture = ExtResource( 30 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="RectangleTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ +"UIButtons", +]] +margin_top = 432.0 +margin_right = 32.0 +margin_bottom = 464.0 +rect_min_size = Vector2( 32, 32 ) +mouse_default_cursor_shape = 2 +button_mask = 3 + [node name="Background" type="TextureRect" parent="ToolPanel/PanelContainer/ToolButtons/RectangleTool"] margin_right = 32.0 margin_bottom = 32.0 @@ -404,9 +430,9 @@ __meta__ = { [node name="EllipseTool" type="Button" parent="ToolPanel/PanelContainer/ToolButtons" groups=[ "UIButtons", ]] -margin_top = 432.0 +margin_top = 468.0 margin_right = 32.0 -margin_bottom = 464.0 +margin_bottom = 500.0 rect_min_size = Vector2( 32, 32 ) mouse_default_cursor_shape = 2 button_mask = 3