mirror of
https://github.com/tonytins/CozyPixelStudio.git
synced 2025-08-11 22:14:42 -04:00
Refactoring tools (#281)
* Refactoring tools * Remove unused code * Fixed some inferring errors and added translations * Attempt to fix some Script Errors found in the CI workflow * Fix bucket crash. * Fix static type convert. Co-authored-by: OverloadedOrama <35376950+OverloadedOrama@users.noreply.github.com>
This commit is contained in:
parent
e1724148fc
commit
4a668f71f5
42 changed files with 2489 additions and 2389 deletions
130
src/Tools/Base.gd
Normal file
130
src/Tools/Base.gd
Normal file
|
@ -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])
|
77
src/Tools/Base.tscn
Normal file
77
src/Tools/Base.tscn
Normal file
|
@ -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"]
|
204
src/Tools/Bucket.gd
Normal file
204
src/Tools/Bucket.gd
Normal file
|
@ -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
|
144
src/Tools/Bucket.tscn
Normal file
144
src/Tools/Bucket.tscn
Normal file
|
@ -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"]
|
45
src/Tools/ColorPicker.gd
Normal file
45
src/Tools/ColorPicker.gd
Normal file
|
@ -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)
|
47
src/Tools/ColorPicker.tscn
Normal file
47
src/Tools/ColorPicker.tscn
Normal file
|
@ -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"]
|
494
src/Tools/Draw.gd
Normal file
494
src/Tools/Draw.gd
Normal file
|
@ -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
|
114
src/Tools/Draw.tscn
Normal file
114
src/Tools/Draw.tscn
Normal file
|
@ -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"]
|
75
src/Tools/Eraser.gd
Normal file
75
src/Tools/Eraser.gd
Normal file
|
@ -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)
|
7
src/Tools/Eraser.tscn
Normal file
7
src/Tools/Eraser.tscn
Normal file
|
@ -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 )
|
112
src/Tools/LightenDarken.gd
Normal file
112
src/Tools/LightenDarken.gd
Normal file
|
@ -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())
|
68
src/Tools/LightenDarken.tscn
Normal file
68
src/Tools/LightenDarken.tscn
Normal file
|
@ -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"]
|
68
src/Tools/Pencil.gd
Normal file
68
src/Tools/Pencil.gd
Normal file
|
@ -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)
|
7
src/Tools/Pencil.tscn
Normal file
7
src/Tools/Pencil.tscn
Normal file
|
@ -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 )
|
59
src/Tools/RectSelect.gd
Normal file
59
src/Tools/RectSelect.gd
Normal file
|
@ -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]
|
20
src/Tools/RectSelect.tscn
Normal file
20
src/Tools/RectSelect.tscn
Normal file
|
@ -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
|
48
src/Tools/Zoom.gd
Normal file
48
src/Tools/Zoom.gd
Normal file
|
@ -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
|
75
src/Tools/Zoom.tscn
Normal file
75
src/Tools/Zoom.tscn
Normal file
|
@ -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"]
|
Loading…
Add table
Add a link
Reference in a new issue