Добавил персонажа и движение на курсор
9
LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
||||
# MIT License
|
||||
|
||||
Copyright 2022 Gennady "Don Tnowe" Krupenyov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
38
README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Sprite Painter
|
||||
|
||||
Actually, it's not just sprites it paints.
|
||||
|
||||
Image editor right within Godot 4!
|
||||
|
||||
Just press the colorful button above the viewport to edit a Node's texture, or an image file in the project folder!
|
||||
|
||||
- **Box and Wand selections**, which mask effects of drawing tools. The underlying pixels can be dragged and dropped elsewhere;
|
||||
- 1-pixel **pencil** - can erase if secondary color is transparent;
|
||||
- Round **brush** with **eraser** and smooth option;
|
||||
- A classic **bucket** tool with a **gradient** variant;
|
||||
- **Line** drawing;
|
||||
- A number of **shapes** to shape your graphics;
|
||||
- And a bit of **scripting** support.
|
||||
|
||||
Other features:
|
||||
- Quick operations on the image: flip, rotate, and resize;
|
||||
- Press Alt to pick color from editor window;
|
||||
- Grid overlay for Sprites with Frames and Maps with Tiles;
|
||||
- Region overlays for Atlastextures and Spriteframes;
|
||||
- Full Undo support.
|
||||
|
||||
#
|
||||
|
||||
Made by Don Tnowe in 2022.
|
||||
|
||||
[My Website](https://redbladegames.netlify.app)
|
||||
|
||||
[My Twitter](https://twitter.com/don_tnowe)
|
||||
|
||||
[My Itch](https://don-tnowe.itch.io)
|
||||
|
||||
Copying and Modification is allowed in accordance to the MIT license, full text is included.
|
||||
|
||||
Some shader code is from [https://iquilezles.org/articles](https://iquilezles.org/articles), released under the MIT License.
|
||||
|
||||
Icon made in Sprite Painter [*tm*]
|
16
Scenes/player.gd
Normal file
@ -0,0 +1,16 @@
|
||||
extends CharacterBody2D
|
||||
|
||||
var speed = 300
|
||||
var click_position = Vector2()
|
||||
var target_position = Vector2()
|
||||
|
||||
func _ready():
|
||||
click_position = position
|
||||
func _physics_process(delta):
|
||||
if Input.is_action_just_pressed("left_click"):
|
||||
click_position = get_global_mouse_position()
|
||||
|
||||
if (position.distance_to(click_position)) >3:
|
||||
target_position = (click_position - position).normalized()
|
||||
velocity = target_position*speed
|
||||
move_and_slide()
|
17
Scenes/player.tscn
Normal file
@ -0,0 +1,17 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dtttpb73i7qt2"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://blmo4uqveqgtu" path="res://assets/Amogus.png" id="1_27e8i"]
|
||||
[ext_resource type="Script" path="res://Scenes/player.gd" id="1_xdifv"]
|
||||
|
||||
[sub_resource type="CircleShape2D" id="CircleShape2D_ounmr"]
|
||||
radius = 3.16228
|
||||
|
||||
[node name="Player" type="CharacterBody2D"]
|
||||
script = ExtResource("1_xdifv")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
texture = ExtResource("1_27e8i")
|
||||
|
||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||
position = Vector2(0, 1)
|
||||
shape = SubResource("CircleShape2D_ounmr")
|
9
addons/sprite_painter/LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
||||
# MIT License
|
||||
|
||||
Copyright 2022 Gennady "Don Tnowe" Krupenyov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
16
addons/sprite_painter/editor_icon_button.gd
Normal file
@ -0,0 +1,16 @@
|
||||
@tool
|
||||
extends Button
|
||||
|
||||
@export var icon_name := "Node"
|
||||
# set(v):
|
||||
# _set_icon_name(v)
|
||||
|
||||
|
||||
func _set_icon_name(v):
|
||||
icon_name = v
|
||||
if has_theme_icon(v, "EditorIcons"):
|
||||
icon = get_theme_icon(v, "EditorIcons")
|
||||
|
||||
|
||||
func _ready():
|
||||
_set_icon_name(icon_name)
|
1
addons/sprite_painter/graphics/circle_shape_2d.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1038.4a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 -6-6z" fill="none" stroke="#5fb2ff" stroke-linejoin="round" stroke-width="2" transform="translate(0 -1036.4)"/></svg>
|
After Width: | Height: | Size: 273 B |
37
addons/sprite_painter/graphics/circle_shape_2d.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cpxsxp2vr8kc6"
|
||||
path="res://.godot/imported/circle_shape_2d.svg-82c4246092358c78c496ed3aeadc38e1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sprite_painter/graphics/circle_shape_2d.svg"
|
||||
dest_files=["res://.godot/imported/circle_shape_2d.svg-82c4246092358c78c496ed3aeadc38e1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
13
addons/sprite_painter/graphics/curve_texture.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://rvkxkfn8dol2"]
|
||||
|
||||
[sub_resource type="Image" id="Image_fmgcp"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 225, 225, 225, 51, 255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 50, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 112, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 112, 224, 224, 224, 254, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 17, 225, 225, 225, 34, 255, 255, 255, 0, 255, 255, 255, 4, 255, 255, 255, 4, 255, 255, 255, 4, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 160, 224, 224, 224, 194, 224, 224, 224, 32, 224, 224, 224, 8, 224, 224, 224, 104, 255, 255, 255, 6, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 224, 224, 224, 96, 224, 224, 224, 192, 224, 224, 224, 255, 224, 224, 224, 232, 227, 227, 227, 9, 224, 224, 224, 107, 224, 224, 224, 255, 226, 226, 226, 35, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 112, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 254, 225, 225, 225, 100, 255, 255, 255, 3, 224, 224, 224, 211, 224, 224, 224, 201, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 225, 225, 225, 75, 255, 255, 255, 0, 225, 225, 225, 124, 224, 224, 224, 255, 226, 226, 226, 79, 255, 255, 255, 0, 231, 231, 231, 21, 225, 225, 225, 67, 238, 238, 238, 15, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 234, 234, 234, 12, 224, 224, 224, 139, 224, 224, 224, 255, 224, 224, 224, 153, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 51, 226, 226, 226, 77, 226, 226, 226, 86, 224, 224, 224, 123, 224, 224, 224, 178, 224, 224, 224, 238, 224, 224, 224, 254, 224, 224, 224, 140, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 228, 228, 228, 37, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 253, 224, 224, 224, 211, 224, 224, 224, 153, 228, 228, 228, 47, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 42, 226, 226, 226, 52, 226, 226, 226, 44, 238, 238, 238, 15, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_fmgcp")
|
BIN
addons/sprite_painter/graphics/diags.png
Normal file
After Width: | Height: | Size: 719 B |
34
addons/sprite_painter/graphics/diags.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c7nymhfcdr3hl"
|
||||
path="res://.godot/imported/diags.png-8f1f755b2561632401e027e528c97db7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sprite_painter/graphics/diags.png"
|
||||
dest_files=["res://.godot/imported/diags.png-8f1f755b2561632401e027e528c97db7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
48
addons/sprite_painter/graphics/diamond_shape_2d.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="DiamondShape2D.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="24.306796"
|
||||
inkscape:cx="0.90509668"
|
||||
inkscape:cy="4.8134687"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1057"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<rect
|
||||
fill="none"
|
||||
height="8.3526468"
|
||||
rx="1.1832915e-05"
|
||||
stroke="#5fb2ff"
|
||||
stroke-linejoin="round"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="2.00051"
|
||||
width="8.3526468"
|
||||
x="-4.1989856"
|
||||
y="7.0055852"
|
||||
id="rect2"
|
||||
transform="matrix(0.71834136,-0.6956908,0.71834136,0.6956908,0,0)" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
37
addons/sprite_painter/graphics/diamond_shape_2d.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://drjoeyt7e6kwg"
|
||||
path="res://.godot/imported/diamond_shape_2d.svg-c7409b07122604b74671fded8f79cf9b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sprite_painter/graphics/diamond_shape_2d.svg"
|
||||
dest_files=["res://.godot/imported/diamond_shape_2d.svg-c7409b07122604b74671fded8f79cf9b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
13
addons/sprite_painter/graphics/distraction_free.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://b5v27x2n2je75"]
|
||||
|
||||
[sub_resource type="Image" id="Image_my53x"]
|
||||
data = {
|
||||
"data": PackedByteArray(225, 225, 225, 17, 225, 225, 225, 68, 225, 225, 225, 68, 225, 225, 225, 68, 224, 224, 224, 24, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 16, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 180, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 142, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 146, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 181, 225, 225, 225, 142, 224, 224, 224, 255, 224, 224, 224, 139, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 144, 224, 224, 224, 255, 224, 224, 224, 138, 224, 224, 224, 184, 224, 224, 224, 64, 233, 233, 233, 23, 255, 255, 255, 7, 255, 255, 255, 0, 224, 224, 224, 139, 224, 224, 224, 123, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 127, 225, 225, 225, 134, 255, 255, 255, 0, 255, 255, 255, 7, 233, 233, 233, 23, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 255, 255, 255, 7, 255, 255, 255, 0, 224, 224, 224, 144, 225, 225, 225, 127, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 132, 224, 224, 224, 139, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 24, 224, 224, 224, 64, 224, 224, 224, 184, 224, 224, 224, 146, 224, 224, 224, 255, 225, 225, 225, 134, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 139, 224, 224, 224, 255, 225, 225, 225, 142, 224, 224, 224, 186, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 138, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 142, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 184, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 186, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 224, 224, 224, 16, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 233, 233, 233, 23, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 24, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 16),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_my53x")
|
13
addons/sprite_painter/graphics/h_size.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://diqj6upxhiiko"]
|
||||
|
||||
[sub_resource type="Image" id="Image_buorn"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 184, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 184, 255, 255, 255, 7, 255, 255, 255, 0, 224, 224, 224, 8, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 255, 224, 224, 224, 184, 255, 255, 255, 7, 255, 255, 255, 7, 224, 224, 224, 184, 224, 224, 224, 255, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 192, 224, 224, 224, 255, 225, 225, 225, 183, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 7, 224, 224, 224, 184, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 183, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 7, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_buorn")
|
41
addons/sprite_painter/graphics/hex_shape_2d.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="HexShape2D.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="5.2149125"
|
||||
inkscape:cy="8.9051261"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1057"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
id="rect2"
|
||||
style="fill:none;stroke:#5fb2ff;stroke-width:2.00051;stroke-linejoin:round;stroke-miterlimit:10"
|
||||
transform="matrix(0.71834136,-0.6956908,0.71834136,0.6956908,0,0)"
|
||||
d="m -1.5727512,5.4706195 5.7264006,1.5349657 1.19e-5,1.18e-5 1.5124976,5.703909 -4.4560086,4.456008 -5.409124,-1.807282 -1.19e-5,-1.2e-5 -1.8297623,-5.4316038 z"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
37
addons/sprite_painter/graphics/hex_shape_2d.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://43n8i7bhg4he"
|
||||
path="res://.godot/imported/hex_shape_2d.svg-33b4a57aed136855fbb5d791f29eb5bb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sprite_painter/graphics/hex_shape_2d.svg"
|
||||
dest_files=["res://.godot/imported/hex_shape_2d.svg-33b4a57aed136855fbb5d791f29eb5bb.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
13
addons/sprite_painter/graphics/mesh_texture.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://dnnjnka3gsci7"]
|
||||
|
||||
[sub_resource type="Image" id="Image_i608h"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 42, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 226, 224, 224, 224, 255, 224, 224, 224, 164, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 168, 224, 224, 224, 255, 224, 224, 224, 225, 255, 255, 255, 6, 224, 224, 224, 41, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 40, 255, 255, 255, 0, 224, 224, 224, 165, 224, 224, 224, 254, 226, 226, 226, 35, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 229, 229, 229, 38, 224, 224, 224, 255, 224, 224, 224, 163, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 31, 225, 225, 225, 17, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 255, 255, 255, 0, 237, 237, 237, 14, 225, 225, 225, 68, 224, 224, 224, 224, 224, 224, 224, 180, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 224, 224, 224, 24, 225, 225, 225, 182, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 245, 225, 225, 225, 84, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 252, 226, 226, 226, 35, 224, 224, 224, 80, 224, 224, 224, 80, 224, 224, 224, 80, 224, 224, 224, 80, 226, 226, 226, 35, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 166, 224, 224, 224, 255, 228, 228, 228, 37, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 229, 229, 229, 39, 224, 224, 224, 255, 224, 224, 224, 165, 255, 255, 255, 0, 225, 225, 225, 42, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 40, 255, 255, 255, 6, 224, 224, 224, 225, 224, 224, 224, 255, 224, 224, 224, 165, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 225, 225, 225, 166, 224, 224, 224, 255, 224, 224, 224, 224, 255, 255, 255, 6, 255, 255, 255, 0, 255, 255, 255, 6, 224, 224, 224, 40, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 40, 255, 255, 255, 6, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_i608h")
|
1
addons/sprite_painter/graphics/rect_shape_2d.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><rect fill="none" height="8" rx=".000017" stroke="#5fb2ff" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" width="12" x="2" y="4"/></svg>
|
After Width: | Height: | Size: 237 B |
37
addons/sprite_painter/graphics/rect_shape_2d.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cfon2trqh4c3k"
|
||||
path="res://.godot/imported/rect_shape_2d.svg-d16c8b9085065dfd954a4786aa34e9fe.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sprite_painter/graphics/rect_shape_2d.svg"
|
||||
dest_files=["res://.godot/imported/rect_shape_2d.svg-d16c8b9085065dfd954a4786aa34e9fe.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
13
addons/sprite_painter/graphics/rotate_left.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://jpxduo4o6vce"]
|
||||
|
||||
[sub_resource type="Image" id="Image_f5vrx"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 4, 224, 224, 224, 65, 224, 224, 224, 112, 224, 224, 224, 90, 230, 230, 230, 30, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 40, 224, 224, 224, 204, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 248, 224, 224, 224, 120, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 230, 230, 230, 10, 224, 224, 224, 220, 224, 224, 224, 230, 225, 225, 225, 83, 238, 238, 238, 15, 229, 229, 229, 38, 224, 224, 224, 164, 224, 224, 224, 255, 224, 224, 224, 98, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 113, 224, 224, 224, 254, 228, 228, 228, 55, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 184, 224, 224, 224, 236, 255, 255, 255, 1, 255, 255, 255, 1, 255, 255, 255, 4, 225, 225, 225, 169, 225, 225, 225, 216, 255, 255, 255, 4, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 89, 224, 224, 224, 255, 229, 229, 229, 38, 227, 227, 227, 9, 224, 224, 224, 209, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 214, 232, 232, 232, 11, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 91, 224, 224, 224, 255, 228, 228, 228, 37, 255, 255, 255, 0, 229, 229, 229, 39, 224, 224, 224, 244, 224, 224, 224, 246, 226, 226, 226, 44, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 187, 224, 224, 224, 235, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 90, 224, 224, 224, 97, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 224, 224, 224, 40, 225, 225, 225, 167, 224, 224, 224, 255, 226, 226, 226, 95, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 247, 224, 224, 224, 115, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 230, 230, 230, 30, 224, 224, 224, 89, 228, 228, 228, 28, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_f5vrx")
|
13
addons/sprite_painter/graphics/rotate_right.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://hc35i3cvcupp"]
|
||||
|
||||
[sub_resource type="Image" id="Image_ts5bs"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 231, 231, 231, 31, 225, 225, 225, 91, 224, 224, 224, 113, 224, 224, 224, 66, 255, 255, 255, 5, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 224, 224, 224, 121, 224, 224, 224, 250, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 205, 224, 224, 224, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 98, 224, 224, 224, 255, 224, 224, 224, 164, 224, 224, 224, 40, 224, 224, 224, 16, 225, 225, 225, 83, 224, 224, 224, 230, 224, 224, 224, 220, 232, 232, 232, 11, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 2, 224, 224, 224, 236, 224, 224, 224, 186, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 227, 227, 227, 53, 224, 224, 224, 254, 224, 224, 224, 115, 255, 255, 255, 0, 255, 255, 255, 0, 227, 227, 227, 36, 224, 224, 224, 255, 225, 225, 225, 93, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 4, 226, 226, 226, 214, 226, 226, 226, 171, 255, 255, 255, 4, 255, 255, 255, 0, 226, 226, 226, 35, 224, 224, 224, 255, 226, 226, 226, 94, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 232, 232, 232, 11, 224, 224, 224, 214, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 207, 224, 224, 224, 8, 255, 255, 255, 1, 224, 224, 224, 234, 224, 224, 224, 189, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 43, 224, 224, 224, 245, 224, 224, 224, 242, 228, 228, 228, 37, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 95, 224, 224, 224, 255, 224, 224, 224, 168, 224, 224, 224, 41, 255, 255, 255, 2, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 97, 224, 224, 224, 88, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 225, 225, 225, 116, 224, 224, 224, 248, 224, 224, 224, 255, 225, 225, 225, 68, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 228, 228, 228, 28, 224, 224, 224, 88, 224, 224, 224, 33, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_ts5bs")
|
13
addons/sprite_painter/graphics/tool_scale.tres
Normal file
@ -0,0 +1,13 @@
|
||||
[gd_resource type="ImageTexture" load_steps=2 format=3 uid="uid://ro5r4gstjow0"]
|
||||
|
||||
[sub_resource type="Image" id="Image_4da7w"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 226, 226, 226, 35, 224, 224, 224, 65, 224, 224, 224, 66, 225, 225, 225, 67, 227, 227, 227, 54, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 229, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 50, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 34, 224, 224, 224, 64, 224, 224, 224, 195, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 229, 229, 229, 19, 224, 224, 224, 224, 225, 225, 225, 191, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 225, 225, 225, 75, 224, 224, 224, 74, 255, 255, 255, 1, 227, 227, 227, 18, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 76, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 72, 255, 255, 255, 0, 225, 225, 225, 34, 224, 224, 224, 226, 224, 224, 224, 33, 226, 226, 226, 35, 224, 224, 224, 229, 225, 225, 225, 34, 255, 255, 255, 0, 225, 225, 225, 75, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 72, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 64, 229, 229, 229, 19, 255, 255, 255, 1, 224, 224, 224, 72, 224, 224, 224, 72, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 195, 224, 224, 224, 224, 227, 227, 227, 18, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 64, 224, 224, 224, 255, 224, 224, 224, 255, 225, 225, 225, 191, 224, 224, 224, 64, 225, 225, 225, 34, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 51, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 226, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 50, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 64, 224, 224, 224, 33, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 12,
|
||||
"mipmaps": false,
|
||||
"width": 12
|
||||
}
|
||||
|
||||
[resource]
|
||||
image = SubResource("Image_4da7w")
|
40
addons/sprite_painter/graphics/triangle_shape_2d.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="TriangleShape2D.svg"
|
||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
showgrid="false"
|
||||
inkscape:zoom="24.306796"
|
||||
inkscape:cx="0.90509668"
|
||||
inkscape:cy="4.8134687"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1057"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
id="rect2"
|
||||
style="fill:none;stroke:#5fb2ff;stroke-width:2;stroke-linejoin:round;stroke-miterlimit:10"
|
||||
d="m 2.000017,2 c 0,0 4.7752562,4.7752562 11.999983,11.999983 0,9e-6 -8e-6,1.7e-5 -1.7e-5,1.7e-5 H 2.000017 C 2.0000076,14 2,13.999992 2,13.999983 V 2.000017 c 0,-9.4e-6 8.305003,8.304969 1.7e-5,-1.7e-5 z"
|
||||
sodipodi:nodetypes="ccssssc" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
37
addons/sprite_painter/graphics/triangle_shape_2d.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://yunqetk64l50"
|
||||
path="res://.godot/imported/triangle_shape_2d.svg-215872cca079d243eb964f3debc05ac8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/sprite_painter/graphics/triangle_shape_2d.svg"
|
||||
dest_files=["res://.godot/imported/triangle_shape_2d.svg-215872cca079d243eb964f3debc05ac8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
38
addons/sprite_painter/image_scripts/Adjust HSV.gd
Normal file
@ -0,0 +1,38 @@
|
||||
extends ImageScript
|
||||
|
||||
|
||||
func _get_param_list():
|
||||
return [
|
||||
[
|
||||
"Hue",
|
||||
SCRIPT_PARAM_INT,
|
||||
0,
|
||||
[0, 360]
|
||||
],
|
||||
[
|
||||
"Saturation",
|
||||
SCRIPT_PARAM_INT,
|
||||
0,
|
||||
[-100, +100]
|
||||
],
|
||||
[
|
||||
"Value",
|
||||
SCRIPT_PARAM_INT,
|
||||
0,
|
||||
[-100, +100]
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
func _get_image(new_image, selection):
|
||||
var pix : Color
|
||||
for i in new_image.get_width():
|
||||
for j in new_image.get_width():
|
||||
if !selection.get_bit(i, j): continue
|
||||
pix = new_image.get_pixel(i, j)
|
||||
pix.h += get_param("Hue") / 360.0
|
||||
pix.s *= 1.0 + get_param("Saturation") * 0.01
|
||||
pix.v *= 1.0 + get_param("Value") * 0.01
|
||||
new_image.set_pixel(i, j, pix)
|
||||
|
||||
return new_image
|
52
addons/sprite_painter/image_scripts/Channel Curves.gd
Normal file
@ -0,0 +1,52 @@
|
||||
extends ImageScript
|
||||
|
||||
|
||||
func _get_param_list():
|
||||
var default_curve = Curve.new()
|
||||
default_curve.add_point(Vector2.ZERO, 1, 1)
|
||||
default_curve.add_point(Vector2.ONE, 1, 1)
|
||||
return [
|
||||
[
|
||||
"Red",
|
||||
SCRIPT_PARAM_RESOURCE,
|
||||
default_curve.duplicate(),
|
||||
"Curve",
|
||||
],
|
||||
[
|
||||
"Green",
|
||||
SCRIPT_PARAM_RESOURCE,
|
||||
default_curve.duplicate(),
|
||||
"Curve",
|
||||
],
|
||||
[
|
||||
"Blue",
|
||||
SCRIPT_PARAM_RESOURCE,
|
||||
default_curve.duplicate(),
|
||||
"Curve",
|
||||
],
|
||||
[
|
||||
"Alpha",
|
||||
SCRIPT_PARAM_RESOURCE,
|
||||
default_curve.duplicate(),
|
||||
"Curve",
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
func _get_image(new_image, selection):
|
||||
var r_curve = get_param("Red")
|
||||
var g_curve = get_param("Green")
|
||||
var b_curve = get_param("Blue")
|
||||
var a_curve = get_param("Alpha")
|
||||
var pix : Color
|
||||
for i in new_image.get_width():
|
||||
for j in new_image.get_width():
|
||||
if !selection.get_bit(i, j): continue
|
||||
pix = new_image.get_pixel(i, j)
|
||||
pix.r = r_curve.sample(pix.r)
|
||||
pix.g = g_curve.sample(pix.g)
|
||||
pix.b = b_curve.sample(pix.b)
|
||||
pix.a = a_curve.sample(pix.a)
|
||||
new_image.set_pixel(i, j, pix)
|
||||
|
||||
return new_image
|
68
addons/sprite_painter/image_scripts/Constrain to Palette.gd
Normal file
@ -0,0 +1,68 @@
|
||||
extends ImageScript
|
||||
|
||||
|
||||
func _get_param_list():
|
||||
return [
|
||||
[
|
||||
"Palette Texture",
|
||||
SCRIPT_PARAM_RESOURCE,
|
||||
# Does not work well on gradients: doesn't consider Interp Mode and Width
|
||||
# so with no proper spatial partitioning takes an eternity
|
||||
# GradientTexture1D.new(),
|
||||
null,
|
||||
"Texture2D"
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
func _get_image(new_image, selection):
|
||||
if get_param("Palette Texture") == null:
|
||||
return new_image
|
||||
|
||||
var palette_img = get_param("Palette Texture").get_image()
|
||||
if palette_img == null:
|
||||
return new_image
|
||||
|
||||
var palette = {}
|
||||
var pix : Color
|
||||
for i in palette_img.get_width():
|
||||
for j in palette_img.get_height():
|
||||
pix = palette_img.get_pixel(i, j)
|
||||
if pix.a > 0.01:
|
||||
palette[Color(pix, 1.0)] = true
|
||||
|
||||
var image_width = new_image.get_width()
|
||||
var image_color_mapping = {}
|
||||
var nearest_color : Color
|
||||
for i in new_image.get_width():
|
||||
for j in new_image.get_height():
|
||||
pix = new_image.get_pixel(i, j)
|
||||
if image_color_mapping.has(pix):
|
||||
if selection.get_bit(i, j):
|
||||
new_image.set_pixel(i, j, Color(image_color_mapping[pix], pix.a))
|
||||
|
||||
elif selection.get_bit(i, j):
|
||||
# I'll rewrite this into a proper spatial algo access later,
|
||||
# bruteforce will do for now
|
||||
nearest_color = Color(pick_nearest(pix, palette), 1.0)
|
||||
image_color_mapping[Color(pix, 1.0)] = nearest_color
|
||||
new_image.set_pixel(i, j, Color(nearest_color, pix.a))
|
||||
|
||||
return new_image
|
||||
|
||||
|
||||
func pick_nearest(to : Color, from : Dictionary):
|
||||
var nearest_dist := INF
|
||||
var nearest : Color
|
||||
var cur_dist : float
|
||||
for x in from:
|
||||
cur_dist = (
|
||||
(x.r - to.r) * (x.r - to.r)
|
||||
+ (x.g - to.g) * (x.g - to.g)
|
||||
+ (x.b - to.b) * (x.b - to.b)
|
||||
)
|
||||
if cur_dist < nearest_dist:
|
||||
nearest = x
|
||||
nearest_dist = cur_dist
|
||||
|
||||
return nearest
|
87
addons/sprite_painter/image_scripts/Outline.gd
Normal file
@ -0,0 +1,87 @@
|
||||
extends ImageScript
|
||||
|
||||
var sample_modes = [
|
||||
[Vector2(0, 1), Vector2(1, 0), Vector2(-1, 0), Vector2(0, -1)],
|
||||
range(8).map(func(i):return Vector2(cos(i * PI * 0.25), sin(i * PI * 0.25))),
|
||||
range(16).map(func(i):return Vector2(cos(i * PI * 0.125), sin(i * PI * 0.125))),
|
||||
# The following modes sample two rings around the pixel,
|
||||
# resulting in a more precise thick outline around thin objects
|
||||
range(16).map(func(i):return ceil((i + 1) * 0.125) * 0.5 * Vector2(cos(i * PI * 0.25), sin(i * PI * 0.25))),
|
||||
range(32).map(func(i):return ceil((i + 1) * 0.0625) * 0.5 * Vector2(cos(i * PI * 0.125), sin(i * PI * 0.125))),
|
||||
]
|
||||
|
||||
|
||||
func _get_param_list():
|
||||
return [
|
||||
[
|
||||
"Width",
|
||||
SCRIPT_PARAM_INT,
|
||||
1,
|
||||
[1, 150]
|
||||
],
|
||||
[
|
||||
"Color",
|
||||
SCRIPT_PARAM_COLOR,
|
||||
Color.BLACK
|
||||
],
|
||||
[
|
||||
"Samples",
|
||||
SCRIPT_PARAM_ENUM,
|
||||
0,
|
||||
["4 (Naive)", "8", "16", "4+4 (Wide Line)", "8+8 (Wide Line Ultra)"]
|
||||
],
|
||||
[
|
||||
"Mode",
|
||||
SCRIPT_PARAM_ENUM,
|
||||
0,
|
||||
["Outline + Image", "Just Outline"]
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
func _get_image(new_image, selection):
|
||||
var line_color = get_param("Color")
|
||||
var width = get_param("Width")
|
||||
var line_only = get_param("Mode") == 1
|
||||
var samples = sample_modes[get_param("Samples")]
|
||||
var image_size = new_image.get_size()
|
||||
var new_new_image = Image.create_from_data(
|
||||
image_size.x,
|
||||
image_size.y,
|
||||
false,
|
||||
new_image.get_format(),
|
||||
new_image.get_data()
|
||||
)
|
||||
var pix : Color
|
||||
var pix_outline_alpha := 0.0
|
||||
for i in new_image.get_width():
|
||||
for j in new_image.get_height():
|
||||
if !selection.get_bit(i, j): continue
|
||||
pix = new_image.get_pixel(i, j)
|
||||
if pix.a == 1.0:
|
||||
if line_only:
|
||||
new_new_image.set_pixel(i, j, Color.TRANSPARENT)
|
||||
|
||||
continue
|
||||
|
||||
pix_outline_alpha = 0.0
|
||||
for x in samples:
|
||||
if pix_outline_alpha >= line_color.a: break
|
||||
if ImageFillTools.is_out_of_bounds(Vector2(i, j) + x * width, image_size):
|
||||
continue
|
||||
|
||||
pix_outline_alpha = max(pix_outline_alpha, min(
|
||||
line_color.a,
|
||||
new_image.get_pixelv(Vector2(i, j) + x * width).a
|
||||
))
|
||||
|
||||
if pix_outline_alpha > 0.0:
|
||||
if line_only:
|
||||
pix = Color(line_color, pix_outline_alpha)
|
||||
|
||||
else:
|
||||
pix = Color(line_color, pix_outline_alpha).blend(pix)
|
||||
|
||||
new_new_image.set_pixel(i, j, pix)
|
||||
|
||||
return new_new_image
|
@ -0,0 +1,140 @@
|
||||
extends ImageScript
|
||||
|
||||
const ROOT2DIV2 = 0.7071
|
||||
|
||||
|
||||
func _get_param_list():
|
||||
var default_curve = Curve.new()
|
||||
default_curve.add_point(Vector2(0.0, 0.5), 1, 1, Curve.TANGENT_LINEAR, Curve.TANGENT_LINEAR)
|
||||
default_curve.add_point(Vector2(1.0, 0.5), 1, 1, Curve.TANGENT_LINEAR, Curve.TANGENT_LINEAR)
|
||||
return [
|
||||
[
|
||||
"Bevel",
|
||||
SCRIPT_PARAM_INT,
|
||||
1,
|
||||
[1, 150]
|
||||
],
|
||||
[
|
||||
"Bevel Distance",
|
||||
SCRIPT_PARAM_ENUM,
|
||||
0,
|
||||
["Circle", "Square", "Diamond"]
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
func _get_image(new_image, selection):
|
||||
var image_size : Vector2i = new_image.get_size()
|
||||
var closest_edge : Array[Vector2] = []
|
||||
var entropy := {}
|
||||
closest_edge.resize(image_size.x * image_size.y)
|
||||
|
||||
var pix : Color
|
||||
var vec : Vector2
|
||||
for i in image_size.x:
|
||||
for j in image_size.y:
|
||||
pix = new_image.get_pixel(i, j)
|
||||
if pix.a == 0.0:
|
||||
closest_edge[i + j * image_size.x] = Vector2.ZERO
|
||||
|
||||
else:
|
||||
vec = Vector2(0, 0)
|
||||
if i == 0: vec.x -= 1
|
||||
if j == 0: vec.y -= 1
|
||||
if i == image_size.x - 1: vec.x += 1
|
||||
if j == image_size.y - 1: vec.y += 1
|
||||
|
||||
if vec == Vector2.ZERO:
|
||||
vec = Vector2(-INF, -INF)
|
||||
entropy[Vector2(i, j)] = 5
|
||||
|
||||
closest_edge[i + j * image_size.x] = vec
|
||||
|
||||
var neighbors = []
|
||||
var neighbor_pos = []
|
||||
var iters = 0
|
||||
var closest_edge_new : Array[Vector2]
|
||||
while entropy.size() > 0:
|
||||
iters += 1
|
||||
closest_edge_new = closest_edge.duplicate()
|
||||
for pos in entropy.keys():
|
||||
neighbor_pos = [
|
||||
pos + Vector2.RIGHT,
|
||||
pos + Vector2.DOWN,
|
||||
pos + Vector2.LEFT,
|
||||
pos + Vector2.UP,
|
||||
]
|
||||
var any_neighbor_defined = false
|
||||
for x in neighbor_pos:
|
||||
if closest_edge[x.x + x.y * image_size.x] != Vector2(-INF, -INF):
|
||||
any_neighbor_defined = true
|
||||
break
|
||||
|
||||
if any_neighbor_defined:
|
||||
entropy.erase(pos)
|
||||
|
||||
neighbors = [
|
||||
closest_edge[pos.x + 1 + pos.y * image_size.x]
|
||||
if !entropy.has(pos + Vector2.RIGHT) else Vector2(-INF, -INF),
|
||||
closest_edge[pos.x + (pos.y + 1) * image_size.x]
|
||||
if !entropy.has(pos + Vector2.DOWN) else Vector2(-INF, -INF),
|
||||
closest_edge[pos.x - 1 + pos.y * image_size.x]
|
||||
if !entropy.has(pos + Vector2.LEFT) else Vector2(-INF, -INF),
|
||||
closest_edge[pos.x + (pos.y - 1) * image_size.x]
|
||||
if !entropy.has(pos + Vector2.UP) else Vector2(-INF, -INF),
|
||||
]
|
||||
closest_edge_new[pos.x + pos.y * image_size.x] = get_closest_edge(neighbors)
|
||||
|
||||
closest_edge = closest_edge_new
|
||||
|
||||
var bevel = get_param("Bevel")
|
||||
var dist_func = [
|
||||
# Three different distance calculations have their own artifacts..
|
||||
func (x): return x.length_squared() > bevel * 2.0, # Circle
|
||||
func (x): return max(abs(x.x), abs(x.y)) > bevel, # Square
|
||||
func (x): return abs(x.x) + abs(x.y) > bevel * 2.0, # Diamond
|
||||
][get_param("Bevel Distance")]
|
||||
|
||||
var vec3 : Vector3
|
||||
var vec_len : float
|
||||
var vec_dir : Vector2
|
||||
for i in image_size.x:
|
||||
for j in image_size.y:
|
||||
vec = closest_edge[i + j * image_size.x]
|
||||
if vec == Vector2.ZERO || dist_func.call(vec):
|
||||
new_image.set_pixel(i, j, Color(0.5, 0.5, 1.0))
|
||||
continue
|
||||
|
||||
vec_len = vec.length()
|
||||
vec_dir = vec / vec_len
|
||||
vec3 = Vector3.BACK.rotated(
|
||||
Vector3(-vec_dir.y, vec_dir.x, 0.0),
|
||||
PI * 0.25
|
||||
)
|
||||
vec3 = vec3 * Vector3(0.5, -0.5, 1.0) + Vector3(0.5, 0.5, 0.0)
|
||||
|
||||
new_image.set_pixel(i, j, Color(vec3.x, vec3.y, vec3.z))
|
||||
|
||||
return new_image
|
||||
|
||||
|
||||
func get_closest_edge(neighbor_vecs):
|
||||
var lengths = [
|
||||
neighbor_vecs[0].length_squared(),
|
||||
neighbor_vecs[1].length_squared(),
|
||||
neighbor_vecs[2].length_squared(),
|
||||
neighbor_vecs[3].length_squared(),
|
||||
]
|
||||
var closest_length = INF
|
||||
for i in 4:
|
||||
if lengths[i] < closest_length:
|
||||
closest_length = lengths[i]
|
||||
|
||||
var result = Vector2.ZERO
|
||||
var neighbor_dirs = [Vector2.RIGHT, Vector2.DOWN, Vector2.LEFT, Vector2.UP]
|
||||
var coeff = 1.0
|
||||
for i in 4:
|
||||
if lengths[i] == closest_length:
|
||||
result += neighbor_vecs[i] + neighbor_dirs[i]
|
||||
|
||||
return result
|
9
addons/sprite_painter/plugin.cfg
Normal file
@ -0,0 +1,9 @@
|
||||
[plugin]
|
||||
|
||||
name="Sprite Painter"
|
||||
description="Paints Sprites.
|
||||
|
||||
An image editor built right into Godot 4."
|
||||
author="Don Tnowe"
|
||||
version="0.1"
|
||||
script="plugin.gd"
|
181
addons/sprite_painter/plugin.gd
Normal file
@ -0,0 +1,181 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const can_edit_properties := [
|
||||
"texture",
|
||||
"tile_set",
|
||||
"frames", # AnimatedSprite
|
||||
"texture_normal", # TextureButton
|
||||
"texture_progress", # Self explanatory
|
||||
]
|
||||
|
||||
const can_edit_types := [
|
||||
"CompressedTexture2D",
|
||||
"AtlasTexture",
|
||||
"SpriteFrames",
|
||||
"TileSet",
|
||||
]
|
||||
|
||||
var editor_view : Control
|
||||
var editor_2d_vp : Control
|
||||
var editor_3d_vp : Control
|
||||
var enable_buttons : Dictionary
|
||||
var sploinky := Control.new()
|
||||
var sploinky3 := Control.new()
|
||||
var undo_redo : EditorUndoRedoManager
|
||||
|
||||
var overlay_enabled := false
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# Sometimes crashes on startup. Until I find why, let this be here
|
||||
await get_tree().create_timer(2.0)
|
||||
|
||||
var ui := get_editor_interface()
|
||||
|
||||
undo_redo = get_undo_redo()
|
||||
editor_view = load(get_script().resource_path.get_base_dir() + "/src/main.tscn").instantiate()
|
||||
editor_view.editor_interface = ui
|
||||
editor_view.editor_plugin = self
|
||||
editor_view.undo_redo = undo_redo
|
||||
|
||||
_connect_editor_viewports()
|
||||
_add_enable_button(EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU)
|
||||
_add_enable_button(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU)
|
||||
|
||||
main_screen_changed.connect(_on_main_screen_changed)
|
||||
ui.get_selection().selection_changed.connect(_on_selection_changed)
|
||||
|
||||
ui.get_base_control().add_child(editor_view)
|
||||
|
||||
make_visible(false)
|
||||
|
||||
|
||||
func _forward_canvas_gui_input(event):
|
||||
if !overlay_enabled:
|
||||
return false
|
||||
|
||||
return editor_view.handle_input(event)
|
||||
|
||||
|
||||
func _connect_editor_viewports():
|
||||
var mainscreen = get_editor_interface().get_editor_main_screen()
|
||||
mainscreen.resized.connect(_on_editor_resized.bind(mainscreen))
|
||||
for x in mainscreen.get_children():
|
||||
if x.get_class() == "CanvasItemEditor":
|
||||
editor_2d_vp = x
|
||||
editor_view.editor_2d_vp = x
|
||||
x.add_child(sploinky)
|
||||
x.move_child(sploinky, 1)
|
||||
|
||||
if x.get_class() == "Node3DEditor":
|
||||
editor_3d_vp = x
|
||||
editor_view.editor_3d_vp = x
|
||||
x.add_child(sploinky3)
|
||||
x.move_child(sploinky3, 1)
|
||||
|
||||
call_deferred("_on_editor_resized", mainscreen)
|
||||
|
||||
|
||||
func _add_enable_button(container_id):
|
||||
var enable_button = Button.new()
|
||||
add_control_to_container(container_id, enable_button)
|
||||
|
||||
enable_button.add_theme_stylebox_override("normal", StyleBoxEmpty.new())
|
||||
enable_button.add_theme_stylebox_override("hover", StyleBoxEmpty.new())
|
||||
enable_button.add_theme_stylebox_override("pressed", StyleBoxEmpty.new())
|
||||
enable_button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||||
enable_button.icon = enable_button.get_theme_icon("StyleBoxTexture", "EditorIcons")
|
||||
enable_button.text = "Edit Texture..."
|
||||
enable_button.pressed.connect(_on_enable_pressed)
|
||||
enable_button.hide()
|
||||
|
||||
enable_buttons[container_id] = enable_button
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
make_visible(false)
|
||||
editor_view.queue_free()
|
||||
sploinky.queue_free()
|
||||
sploinky3.queue_free()
|
||||
for x in enable_buttons.values():
|
||||
x.queue_free()
|
||||
|
||||
|
||||
func make_visible(visible):
|
||||
if !is_instance_valid(editor_view): return
|
||||
if editor_view.visible == visible: return
|
||||
|
||||
editor_view.visible = visible
|
||||
editor_2d_vp.get_child(0).visible = !visible
|
||||
editor_3d_vp.get_child(0).visible = !visible
|
||||
sploinky.custom_minimum_size.y = editor_2d_vp.get_child(0).size.y
|
||||
sploinky3.custom_minimum_size.y = editor_3d_vp.get_child(0).size.y
|
||||
sploinky.visible = visible
|
||||
sploinky3.visible = visible
|
||||
if visible:
|
||||
editor_view.edit_object(editor_view.edited_object)
|
||||
|
||||
else:
|
||||
editor_view.save_changes()
|
||||
if editor_view.unsaved_image_paths.size() == 0:
|
||||
return
|
||||
|
||||
# print("Saved images: " + str(editor_view.unsaved_image_paths))
|
||||
get_editor_interface()\
|
||||
.get_resource_filesystem()\
|
||||
# I am totally done with this thing freezing the editor forever,
|
||||
# Yes it is more efficient, but stability matters more
|
||||
# .reimport_files(editor_view.unsaved_image_paths)
|
||||
.scan()
|
||||
editor_view.unsaved_image_paths.clear()
|
||||
|
||||
|
||||
func _edit(object):
|
||||
editor_view.edit_object(object)
|
||||
for x in enable_buttons.values():
|
||||
x.show()
|
||||
|
||||
if overlay_enabled:
|
||||
make_visible(true)
|
||||
|
||||
|
||||
func _handles(object):
|
||||
for x in can_edit_properties:
|
||||
if x in object:
|
||||
return true
|
||||
|
||||
for x in can_edit_types:
|
||||
if ClassDB.is_parent_class(object.get_class(), x):
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _on_enable_pressed():
|
||||
overlay_enabled = !overlay_enabled
|
||||
make_visible(overlay_enabled)
|
||||
|
||||
|
||||
func _on_main_screen_changed(screen):
|
||||
overlay_enabled = false
|
||||
make_visible(false)
|
||||
|
||||
|
||||
func _on_selection_changed():
|
||||
var sel = get_editor_interface().get_selection().get_selected_nodes()
|
||||
if sel.size() == 0 || !_handles(sel[-1]):
|
||||
for x in enable_buttons.values():
|
||||
x.hide()
|
||||
|
||||
overlay_enabled = false
|
||||
make_visible(false)
|
||||
|
||||
else:
|
||||
for x in enable_buttons.values():
|
||||
x.show()
|
||||
|
||||
|
||||
func _on_editor_resized(mainscreen : Control):
|
||||
editor_view.size = mainscreen.size
|
||||
editor_view.global_position = mainscreen.global_position
|
60
addons/sprite_painter/shaders/gradient.tres
Normal file
@ -0,0 +1,60 @@
|
||||
[gd_resource type="Shader" format=3 uid="uid://bpswjmm8i01cg"]
|
||||
|
||||
[resource]
|
||||
code = "shader_type canvas_item;
|
||||
render_mode blend_premul_alpha;
|
||||
|
||||
uniform sampler2D gradient;
|
||||
uniform vec2 from;
|
||||
uniform vec2 delta;
|
||||
uniform int type;
|
||||
|
||||
float angle(vec2 dir) {
|
||||
return atan(dir.x / dir.y) / PI * 0.5 + (dir.y < 0.0 ? 0.75 : 0.25);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
if (texture(TEXTURE, UV).r < 0.5) discard;
|
||||
float pos;
|
||||
switch (type)
|
||||
{
|
||||
case 0: // Line
|
||||
// Stolen again from:
|
||||
// https://iquilezles.org/articles/forgot-the-exact-link
|
||||
vec2 delta_rotated = vec2(-delta.y, delta.x);
|
||||
vec2 diff = UV - from;
|
||||
float h = dot(diff, delta_rotated) / dot(delta_rotated, delta_rotated);
|
||||
if (dot(diff, delta) > 0.0)
|
||||
pos = length(diff - h * delta_rotated) / length(delta);
|
||||
|
||||
break;
|
||||
|
||||
case 1: // Line Mirrored
|
||||
vec2 delta_rotated = vec2(-delta.y, delta.x);
|
||||
float h = dot(UV - from, delta_rotated) / dot(delta_rotated, delta_rotated);
|
||||
pos = length(UV - from - h * delta_rotated) / length(delta);
|
||||
break;
|
||||
|
||||
case 2: // Radial
|
||||
pos = length(UV - from) / length(delta);
|
||||
break;
|
||||
|
||||
case 3: // Conic
|
||||
vec2 dir = (UV - from);
|
||||
pos = mod(angle(dir) - angle(delta), 1.0);
|
||||
break;
|
||||
|
||||
case 4: // Bounds
|
||||
vec2 abs_delta = abs(delta);
|
||||
vec2 abs_from = from;
|
||||
if (delta.x < 0.0) abs_from.x -= abs_delta.x;
|
||||
if (delta.y < 0.0) abs_from.y -= abs_delta.y;
|
||||
pos = min(
|
||||
min(UV.x - abs_from.x, abs_delta.x - UV.x + abs_from.x),
|
||||
min(UV.y - abs_from.y, abs_delta.y - UV.y + abs_from.y)
|
||||
) / min(abs_delta.x, abs_delta.y) * 2.0;
|
||||
break;
|
||||
}
|
||||
COLOR = texture(gradient, vec2(clamp(pos, 0.0, 0.999999), 0.5));
|
||||
}
|
||||
"
|
38
addons/sprite_painter/shaders/line.tres
Normal file
@ -0,0 +1,38 @@
|
||||
[gd_resource type="Shader" format=3 uid="uid://c84r6o3a8gfo0"]
|
||||
|
||||
[resource]
|
||||
code = "shader_type canvas_item;
|
||||
render_mode blend_premul_alpha;
|
||||
|
||||
uniform vec4 color;
|
||||
uniform vec2 origin;
|
||||
uniform vec2 delta;
|
||||
uniform float width;
|
||||
uniform bool enable_aa;
|
||||
|
||||
|
||||
float get_sdf(vec2 pos) {
|
||||
if (delta == vec2(0.0)) return float(-1.0);
|
||||
|
||||
float h = clamp(dot(pos, delta) / dot(delta, delta), 0.0, 1.0);
|
||||
return length(pos - h * delta);
|
||||
}
|
||||
|
||||
|
||||
vec4 sdf_to_color(float sdf) {
|
||||
if (enable_aa)
|
||||
return vec4(color.rgb, mix(
|
||||
0.0,
|
||||
color.a,
|
||||
(width * 0.5 - sdf) * 2.0
|
||||
));
|
||||
else
|
||||
return vec4(color.rgb, 1.0 - floor(sdf / (width * 0.5)));
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = sdf_to_color(get_sdf(
|
||||
floor(UV / TEXTURE_PIXEL_SIZE) - origin
|
||||
));
|
||||
}
|
||||
"
|
127
addons/sprite_painter/shaders/shape.tres
Normal file
@ -0,0 +1,127 @@
|
||||
[gd_resource type="Shader" format=3 uid="uid://clpby1t4s1pvy"]
|
||||
|
||||
[resource]
|
||||
code = "shader_type canvas_item;
|
||||
render_mode blend_premul_alpha;
|
||||
|
||||
uniform int shape_index;
|
||||
// if you are reading this file and know
|
||||
// what hint_color is called in 4.0
|
||||
// please open an issue or pull request
|
||||
uniform vec4 color_border;
|
||||
uniform vec4 color_fill;
|
||||
uniform vec2 origin;
|
||||
uniform vec2 shape_size;
|
||||
uniform float border_width;
|
||||
uniform vec2 drag_delta;
|
||||
uniform bool enable_aa;
|
||||
|
||||
|
||||
float get_sdf(vec2 pos, vec2 size) {
|
||||
size -= vec2(1.0);
|
||||
if (size == vec2(0.0)) return -1.0;
|
||||
switch(shape_index) {
|
||||
case 0: // Rect
|
||||
return min(
|
||||
min(pos.x, size.x - pos.x),
|
||||
min(pos.y, size.y - pos.y)
|
||||
);
|
||||
|
||||
case 1: // Ellipse
|
||||
// Stolen from:
|
||||
// https://iquilezles.org/articles/ellipsedist/
|
||||
vec2 extents = size * 0.5;
|
||||
vec2 posf = abs(vec2(pos) - extents);
|
||||
|
||||
vec2 q = extents * (posf - extents);
|
||||
vec2 cs = normalize(q.x < q.y ? vec2(0.01, 1) : vec2(1, 0.01));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
vec2 u = extents * vec2(+cs.x, cs.y);
|
||||
vec2 v = extents * vec2(-cs.y, cs.x);
|
||||
float a = dot(posf - u, v);
|
||||
float c = dot(posf - u, u) + dot(v, v);
|
||||
float b = sqrt(c * c - a * a);
|
||||
cs = vec2(cs.x * b - cs.y * a, cs.y * b + cs.x * a) / c;
|
||||
}
|
||||
|
||||
float d = length(posf - extents * cs);
|
||||
return dot(posf / extents, posf / extents) > 1.0 ? -d : d;
|
||||
|
||||
case 2: // RA Triangle
|
||||
float aspect = size.x / size.y;
|
||||
float distance_to_diag = 0.0;
|
||||
if ((drag_delta.x < 0.0) == (drag_delta.y < 0.0))
|
||||
// Main diag
|
||||
if (drag_delta.x < drag_delta.y)
|
||||
// Bottom filled
|
||||
distance_to_diag = (pos.x - pos.y * aspect);
|
||||
|
||||
else
|
||||
// Top filled
|
||||
// !!! incorrest sdf
|
||||
distance_to_diag = (pos.y * aspect - pos.x);
|
||||
|
||||
else
|
||||
// Secondary diag
|
||||
if (drag_delta.x < -drag_delta.y)
|
||||
// Bottom filled
|
||||
// !!! incorrest sdf
|
||||
distance_to_diag = (pos.y * aspect - size.x + pos.x);
|
||||
|
||||
else
|
||||
// Top filled
|
||||
distance_to_diag = (size.x - pos.x - pos.y * aspect);
|
||||
|
||||
float rect_dist = min(
|
||||
min(pos.x, size.x - pos.x),
|
||||
min(pos.y, size.y - pos.y)
|
||||
);
|
||||
return min(distance_to_diag / aspect, rect_dist);
|
||||
|
||||
case 3: // Diamond
|
||||
vec2 from_center = abs(vec2(pos * 2.0) - size) * 0.5;
|
||||
from_center.y *= size.x / size.y;
|
||||
return size.x * 0.5 - (from_center.x + from_center.y);
|
||||
|
||||
case 4: // Hex
|
||||
if ((drag_delta.x < 0.0) == (drag_delta.y < 0.0)) {
|
||||
pos = pos.yx;
|
||||
size = size.yx;
|
||||
}
|
||||
vec2 diamond_from_center = abs(pos * 2.0 - size) * vec2(0.25, 0.5);
|
||||
return size.x * 0.5 - max(
|
||||
diamond_from_center.x + diamond_from_center.y * size.x / size.y,
|
||||
abs(pos.x - size.x * 0.5)
|
||||
);
|
||||
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
|
||||
vec4 sdf_to_color(float sdf) {
|
||||
// Debug
|
||||
// return vec4(sdf * 0.1, -sdf * 0.1, 0.0, 1.0);
|
||||
if (sdf < -0.5) {
|
||||
return vec4(color_border.rgb, 0.0);
|
||||
}
|
||||
if (sdf < 0.0 && enable_aa) {
|
||||
sdf = (0.5 + sdf) * 2.0;
|
||||
return mix(vec4(color_border.rgb, 0.0), color_border, sdf * sdf);
|
||||
}
|
||||
if (sdf <= border_width - 0.5) {
|
||||
return color_border;
|
||||
}
|
||||
if (sdf < border_width && enable_aa) {
|
||||
sdf = -(sdf - border_width + 0.5) * 2.0;
|
||||
return mix(color_fill, color_border, 1.0 - sdf * sdf);
|
||||
}
|
||||
return color_fill;
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
COLOR = sdf_to_color(get_sdf(
|
||||
floor(UV / TEXTURE_PIXEL_SIZE) - origin,
|
||||
shape_size
|
||||
));
|
||||
}
|
||||
"
|
369
addons/sprite_painter/src/editing_tool.gd
Normal file
@ -0,0 +1,369 @@
|
||||
@tool
|
||||
class_name EditingTool
|
||||
extends VBoxContainer
|
||||
|
||||
enum {
|
||||
TOOL_PROP_BOOL,
|
||||
TOOL_PROP_INT,
|
||||
TOOL_PROP_FLOAT,
|
||||
TOOL_PROP_ENUM,
|
||||
TOOL_PROP_ICON_ENUM,
|
||||
TOOL_PROP_ICON_FLAGS,
|
||||
TOOL_PROP_RESOURCE,
|
||||
TOOL_PROP_FOLDER_SCAN,
|
||||
TOOL_PROP_COLOR,
|
||||
}
|
||||
|
||||
enum {
|
||||
OPERATION_REPLACE,
|
||||
OPERATION_ADD,
|
||||
OPERATION_SUBTRACT,
|
||||
OPERATION_INTERSECTION,
|
||||
OPERATION_XOR,
|
||||
}
|
||||
|
||||
@export var tool_name := "Box Selection"
|
||||
@export_multiline var tool_desc := ""
|
||||
@export var preview_shader : ShaderMaterial
|
||||
@export_enum("None", "When Drawing", "When Active") var image_hide_mode := 0
|
||||
|
||||
var selection : BitMap
|
||||
|
||||
var _last_grid : GridContainer
|
||||
var _hotkey_adjustment_hook : Callable
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
if !visibility_changed.is_connected(_on_visibility_changed):
|
||||
visibility_changed.connect(_on_visibility_changed)
|
||||
|
||||
set_process_shortcut_input(false)
|
||||
|
||||
|
||||
func add_name():
|
||||
var label = Label.new()
|
||||
label.text = tool_name
|
||||
label.size_flags_vertical = SIZE_SHRINK_CENTER | SIZE_FILL
|
||||
add_child(label)
|
||||
add_separator()
|
||||
label = Label.new()
|
||||
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||
label.self_modulate.a = 0.75
|
||||
label.text = tool_desc
|
||||
label.size_flags_vertical = SIZE_SHRINK_CENTER | SIZE_FILL
|
||||
if tool_desc == "": label.hide()
|
||||
add_child(label)
|
||||
|
||||
_last_grid = null
|
||||
|
||||
|
||||
func add_separator():
|
||||
var sep = ColorRect.new()
|
||||
sep.custom_minimum_size = Vector2(0, 2)
|
||||
sep.color = get_theme_color("accent_color", "Editor")
|
||||
sep.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
add_child(sep)
|
||||
|
||||
_last_grid = null
|
||||
|
||||
|
||||
func start_property_grid():
|
||||
var grid = GridContainer.new()
|
||||
grid.columns = 2
|
||||
_last_grid = grid
|
||||
add_child(grid)
|
||||
return grid
|
||||
|
||||
|
||||
func add_property(
|
||||
property_name,
|
||||
default_value,
|
||||
setter : Callable,
|
||||
type : int,
|
||||
hint : Variant = null,
|
||||
hotkey_adjustment = false,
|
||||
):
|
||||
var parent = _last_grid
|
||||
if _last_grid == null:
|
||||
parent = HBoxContainer.new()
|
||||
add_child(parent)
|
||||
|
||||
var label = Label.new()
|
||||
label.size_flags_vertical = SIZE_SHRINK_CENTER | SIZE_FILL
|
||||
label.text = property_name
|
||||
parent.add_child(label)
|
||||
|
||||
var editor
|
||||
match type:
|
||||
TOOL_PROP_BOOL:
|
||||
editor = CheckBox.new()
|
||||
editor.button_pressed = default_value
|
||||
editor.text = "On"
|
||||
editor.toggled.connect(setter)
|
||||
if hotkey_adjustment:
|
||||
_hotkey_adjustment_hook = func(x):
|
||||
editor.button_pressed = !editor.button_pressed
|
||||
setter.call(editor.button_pressed)
|
||||
|
||||
TOOL_PROP_INT, TOOL_PROP_FLOAT:
|
||||
editor = EditorSpinSlider.new()
|
||||
if hint == null:
|
||||
editor.max_value = INF
|
||||
editor.min_value = -INF
|
||||
|
||||
else:
|
||||
editor.min_value = hint[0]
|
||||
editor.max_value = hint[1]
|
||||
editor.hide_slider = false
|
||||
|
||||
editor.value = default_value
|
||||
editor.step = 0.01 if type == TOOL_PROP_FLOAT else 1.0
|
||||
editor.custom_minimum_size.x = 64.0
|
||||
editor.value_changed.connect(setter)
|
||||
if hotkey_adjustment:
|
||||
_hotkey_adjustment_hook = func(x):
|
||||
editor.value += editor.step * x
|
||||
setter.call(editor.value)
|
||||
|
||||
TOOL_PROP_ENUM:
|
||||
if hint.size() != 2:
|
||||
editor = OptionButton.new()
|
||||
for x in hint:
|
||||
editor.add_item(x)
|
||||
|
||||
editor.item_selected.connect(setter)
|
||||
editor.select(default_value)
|
||||
if hotkey_adjustment:
|
||||
_hotkey_adjustment_hook = func(x):
|
||||
editor.select(posmod((editor.get_selected_id() + x), hint.size()))
|
||||
setter.call(editor.get_selected_id())
|
||||
|
||||
else:
|
||||
editor = Button.new()
|
||||
editor.text = hint[default_value]
|
||||
editor.pressed.connect(func():
|
||||
var x = 1 if editor.text == hint[0] else 0
|
||||
editor.text = hint[x]
|
||||
setter.call(x)
|
||||
)
|
||||
if hotkey_adjustment:
|
||||
_hotkey_adjustment_hook = func(x):
|
||||
var new_value = 1 if editor.text == hint[0] else 0
|
||||
editor.text = hint[new_value]
|
||||
setter.call(new_value)
|
||||
|
||||
|
||||
TOOL_PROP_ICON_ENUM, TOOL_PROP_ICON_FLAGS:
|
||||
editor = HBoxContainer.new()
|
||||
var icons = hint if hint is Array else hint.keys()
|
||||
var tooltips = {} if hint is Array else hint
|
||||
var button
|
||||
|
||||
var bgroup = ButtonGroup.new()
|
||||
|
||||
for x in icons:
|
||||
button = load("res://addons/sprite_painter/editor_icon_button.gd").new()
|
||||
button.tooltip_text = tooltips.get(x)
|
||||
button.toggle_mode = true
|
||||
button.add_theme_stylebox_override("pressed", button.get_theme_stylebox("focus", "Button"))
|
||||
button.add_theme_stylebox_override("focus", StyleBoxEmpty.new())
|
||||
|
||||
if x is Texture:
|
||||
button.set_deferred("icon", x)
|
||||
|
||||
else:
|
||||
button._set_icon_name(x)
|
||||
|
||||
editor.add_child(button)
|
||||
if type == TOOL_PROP_ICON_ENUM:
|
||||
button.button_group = bgroup
|
||||
button.toggled.connect(func(toggled):
|
||||
if !toggled: return
|
||||
setter.call(button.get_index())
|
||||
)
|
||||
button.button_pressed = default_value == button.get_index()
|
||||
|
||||
else:
|
||||
button.toggled.connect(func(toggled):
|
||||
default_value[button.get_index()] = toggled
|
||||
setter.call(default_value)
|
||||
)
|
||||
button.button_pressed = default_value[button.get_index()]
|
||||
|
||||
if hotkey_adjustment:
|
||||
_hotkey_adjustment_hook = func(x):
|
||||
var new_value : int
|
||||
for i in editor.get_child_count():
|
||||
if editor.get_child(i).button_pressed:
|
||||
new_value = posmod(i + x, hint.size())
|
||||
editor.get_child(new_value).button_pressed = true
|
||||
editor.get_child(i).button_pressed = false
|
||||
break
|
||||
|
||||
# setter.call(new_value)
|
||||
|
||||
TOOL_PROP_RESOURCE:
|
||||
editor = EditorResourcePicker.new()
|
||||
editor.resource_changed.connect(func(x):
|
||||
setter.call(x)
|
||||
)
|
||||
editor.edited_resource = default_value
|
||||
if !hint is Array || hint.size() > 0:
|
||||
editor.base_type = hint if hint is String else hint[0]
|
||||
|
||||
var plugin_root = get_parent()
|
||||
while !plugin_root is Window:
|
||||
plugin_root = plugin_root.get_parent()
|
||||
if plugin_root is SpritePainterRoot:
|
||||
editor.resource_selected.connect(func(x, inspected):
|
||||
plugin_root.editor_interface.edit_resource(x)
|
||||
)
|
||||
break
|
||||
|
||||
TOOL_PROP_FOLDER_SCAN:
|
||||
editor = OptionButton.new()
|
||||
var folder = (hint if hint is String else hint[0]).trim_suffix("/") + "/"
|
||||
var filenames = DirAccess.get_files_at(folder)
|
||||
|
||||
for x in filenames:
|
||||
editor.add_item(x.get_basename())
|
||||
|
||||
editor.item_selected.connect(func(x):
|
||||
setter.call(load(folder + filenames[x]))
|
||||
)
|
||||
if default_value is int:
|
||||
editor.select(default_value)
|
||||
|
||||
elif default_value is String:
|
||||
default_value = default_value.get_file()
|
||||
|
||||
var found_index = filenames.find(default_value)
|
||||
editor.select(found_index)
|
||||
|
||||
if hotkey_adjustment:
|
||||
_hotkey_adjustment_hook = func(x):
|
||||
editor.select(posmod((editor.get_selected_id() + x), hint.size()))
|
||||
setter.call(load(folder + filenames[editor.get_selected_id()]))
|
||||
|
||||
TOOL_PROP_COLOR:
|
||||
editor = ColorPickerButton.new()
|
||||
editor.color = default_value
|
||||
editor.color_changed.connect(setter)
|
||||
|
||||
|
||||
editor.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
parent.add_child(editor)
|
||||
return editor
|
||||
|
||||
|
||||
func add_text_display():
|
||||
var textbox = LineEdit.new()
|
||||
textbox.editable = false
|
||||
textbox.size_flags_vertical = SIZE_FILL
|
||||
textbox.expand_to_text_length = true
|
||||
add_child(textbox)
|
||||
|
||||
_last_grid = null
|
||||
return textbox
|
||||
|
||||
|
||||
func add_button_panel(labels : Array[String]):
|
||||
var container = HFlowContainer.new()
|
||||
for x in labels:
|
||||
var new_button = Button.new()
|
||||
new_button.text = x
|
||||
new_button.size_flags_horizontal = SIZE_EXPAND_FILL
|
||||
container.add_child(new_button)
|
||||
|
||||
add_child(container)
|
||||
|
||||
_last_grid = null
|
||||
return container
|
||||
|
||||
|
||||
func is_out_of_bounds(pos : Vector2i, rect_size : Vector2i):
|
||||
return (
|
||||
pos.x < 0 || pos.y < 0
|
||||
|| pos.x >= rect_size.x || pos.y >= rect_size.y
|
||||
)
|
||||
|
||||
|
||||
func get_rect_from_drag(start_pos, end_pos, squarify : bool = false):
|
||||
var rect = Rect2i(start_pos, end_pos - start_pos)
|
||||
if !squarify:
|
||||
return rect.abs().grow_individual(0, 0, 1, 1)
|
||||
|
||||
var max_side = maxi(abs(rect.size.x), abs(rect.size.y))
|
||||
var square = Rect2i(rect.position, Vector2i(max_side, max_side))
|
||||
if rect.size.x < 0:
|
||||
square.position.x -= square.size.x
|
||||
|
||||
if rect.size.y < 0:
|
||||
square.position.y -= square.size.y
|
||||
|
||||
return square
|
||||
|
||||
|
||||
|
||||
func is_selection_empty():
|
||||
return selection.get_true_bit_count() == selection.get_size().x * selection.get_size().y
|
||||
|
||||
|
||||
func set_image_pixel(image : Image, x : int, y : int, color : Color):
|
||||
if !is_out_of_bounds(Vector2i(x, y), image.get_size()):
|
||||
if selection.get_bit(x, y):
|
||||
image.set_pixel(x, y, color)
|
||||
|
||||
|
||||
func set_image_pixelv(image : Image, pos : Vector2i, color : Color):
|
||||
if !is_out_of_bounds(pos, image.get_size()):
|
||||
if selection.get_bitv(pos):
|
||||
image.set_pixelv(pos, color)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
printerr("Not implemented: mouse_pressed! (" + get_script().resource_path.get_file() + ")")
|
||||
|
||||
|
||||
func get_affected_rect() -> Rect2i:
|
||||
printerr("Not implemented: get_affected_rect! (" + get_script().resource_path.get_file() + ")")
|
||||
return Rect2i()
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
printerr("Not implemented: mouse_moved! (" + get_script().resource_path.get_file() + ")")
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
printerr("Not implemented: draw_preview! (" + get_script().resource_path.get_file() + ")")
|
||||
|
||||
|
||||
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
pass
|
||||
|
||||
|
||||
func draw_crosshair(image_view : CanvasItem, mouse_position : Vector2i, line_length : int, color : Color):
|
||||
image_view.draw_rect(Rect2i(mouse_position + Vector2i(0, 4), Vector2(1, +line_length)).abs(), color)
|
||||
image_view.draw_rect(Rect2i(mouse_position - Vector2i(0, 3), Vector2(1, -line_length)).abs(), color)
|
||||
image_view.draw_rect(Rect2i(mouse_position + Vector2i(4, 0), Vector2(+line_length, 1)).abs(), color)
|
||||
image_view.draw_rect(Rect2i(mouse_position - Vector2i(3, 0), Vector2(-line_length, 1)).abs(), color)
|
||||
|
||||
|
||||
func _shortcut_input(event : InputEvent):
|
||||
if _hotkey_adjustment_hook.is_null(): return
|
||||
if !event is InputEventKey: return
|
||||
if !event.pressed: return
|
||||
if event.keycode != KEY_BRACKETLEFT:
|
||||
_hotkey_adjustment_hook.call(+1)
|
||||
|
||||
if event.keycode != KEY_BRACKETRIGHT:
|
||||
_hotkey_adjustment_hook.call(-1)
|
||||
|
||||
|
||||
func _on_visibility_changed():
|
||||
set_process_shortcut_input(visible)
|
264
addons/sprite_painter/src/editing_tools/tool_brush.gd
Normal file
@ -0,0 +1,264 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
enum {
|
||||
BRUSH_DRAW,
|
||||
BRUSH_ERASE,
|
||||
BRUSH_CLONE,
|
||||
BRUSH_SHADING,
|
||||
BRUSH_NORMALMAP,
|
||||
}
|
||||
|
||||
@export_enum("Draw", "Erase", "Clone", "Shading", "Normal Map") var brush_type := 0
|
||||
@export var chunk_size := Vector2i(256, 256)
|
||||
@export var max_brush_size := 150
|
||||
@export var max_brush_min_size := 150
|
||||
@export var crosshair_color := Color(0.5, 0.5, 0.5, 0.75)
|
||||
|
||||
var brushsize := 5
|
||||
var brushminsize := 5
|
||||
var brush_offset := Vector2(0.5, 0.5)
|
||||
var hardness := 1.0
|
||||
var opacity := 1.0
|
||||
var pen_flags := [true, false, false]
|
||||
|
||||
var drawing := false
|
||||
var drawing_color1 := Color()
|
||||
var drawing_color2 := Color()
|
||||
var last_edits_chunks := {}
|
||||
var last_edits_textures := {}
|
||||
var last_affected_rect := Rect2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_property("Size", brushsize,
|
||||
func (x):
|
||||
brushsize = x
|
||||
brush_offset = Vector2(0.5, 0.5) * float(int(x) % 2),
|
||||
TOOL_PROP_INT,
|
||||
[1, max_brush_size],
|
||||
true
|
||||
)
|
||||
add_property("Min Size", brushminsize,
|
||||
func (x):
|
||||
brushminsize = x,
|
||||
TOOL_PROP_INT,
|
||||
[1, max_brush_min_size],
|
||||
true
|
||||
)
|
||||
add_property("Hardness", hardness * 100,
|
||||
func (x): hardness = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100]
|
||||
)
|
||||
add_property("Strength", opacity * 100,
|
||||
func (x): opacity = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100]
|
||||
)
|
||||
|
||||
var pressure_options := {
|
||||
"ToolScale" : "Size",
|
||||
"Gradient" : "Opacity",
|
||||
}
|
||||
if brush_type == BRUSH_DRAW:
|
||||
pressure_options["CanvasItemMaterial"] = "Tint"
|
||||
|
||||
add_property("Pen Pressure", pen_flags,
|
||||
func (x): pen_flags = x,
|
||||
TOOL_PROP_ICON_FLAGS,
|
||||
pressure_options
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
drawing = event.pressed
|
||||
drawing_color1 = color1
|
||||
drawing_color2 = color2
|
||||
if drawing:
|
||||
start_drawing(image, event.position)
|
||||
|
||||
else:
|
||||
match brush_type:
|
||||
BRUSH_ERASE:
|
||||
apply_eraser(image)
|
||||
|
||||
_:
|
||||
apply_brush(image)
|
||||
|
||||
|
||||
func start_drawing(image, start_pos):
|
||||
last_edits_chunks.clear()
|
||||
# Break the image up into tiles - small images are faster to edit.
|
||||
for i in ceil(float(image.get_width()) / chunk_size.x):
|
||||
for j in ceil(float(image.get_height()) / chunk_size.y):
|
||||
last_edits_chunks[Vector2i(i, j) * chunk_size] = Image.create(
|
||||
chunk_size.x, chunk_size.y,
|
||||
false, image.get_format()
|
||||
)
|
||||
for k in last_edits_chunks:
|
||||
last_edits_textures[k] = ImageTexture.create_from_image(last_edits_chunks[k])
|
||||
# Copy the image to the tiles. Worse opacity handling,
|
||||
# but with more work can make eraser editing more performant and previewable.
|
||||
# last_edits_chunks[k].blit_rect(image, Rect2i(k, chunk_size), Vector2i.ZERO)
|
||||
|
||||
last_affected_rect = Rect2i(start_pos, Vector2i.ZERO)
|
||||
|
||||
|
||||
func apply_brush(image):
|
||||
for k in last_edits_chunks:
|
||||
image.blend_rect(
|
||||
last_edits_chunks[k],
|
||||
Rect2i(Vector2i.ZERO, chunk_size),
|
||||
k
|
||||
)
|
||||
|
||||
|
||||
func apply_eraser(image):
|
||||
# Cutting off a smaller image does not increase performance.
|
||||
# Must find another way - erasing is very slow.
|
||||
# var new_image = Image.create(last_affected_rect.size.x + 1, last_affected_rect.size.y + 1, false, image.get_format())
|
||||
# new_image.blit_rect(new_image, last_affected_rect, Vector2i.ZERO)
|
||||
var pos : Vector2i
|
||||
for k in last_edits_chunks:
|
||||
var chunk = last_edits_chunks[k]
|
||||
var height = mini(image.get_height() - k.y, chunk.get_height())
|
||||
for i in mini(image.get_width() - k.x, chunk.get_width()):
|
||||
for j in height:
|
||||
# pos = k - last_affected_rect.position + Vector2i(i, j)
|
||||
pos = Vector2i(i + k.x, j + k.y)
|
||||
chunk.set_pixel(
|
||||
i, j,
|
||||
image.get_pixelv(pos) - chunk.get_pixel(i, j)
|
||||
)
|
||||
image.blit_rect(last_edits_chunks[k], Rect2i(Vector2i.ZERO, chunk_size), k)
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
return last_affected_rect.grow_individual(0, 0, 1, 1)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if !drawing: return
|
||||
if event.button_mask & MOUSE_BUTTON_MASK_LEFT != 0.0:
|
||||
stroke(event.position, event.position - event.relative, event.pressure)
|
||||
|
||||
else:
|
||||
stroke(event.position, event.position - event.relative, 1.0)
|
||||
|
||||
|
||||
func stroke(stroke_start : Vector2, stroke_end : Vector2, pressure : float):
|
||||
var rect = Rect2i(stroke_start, Vector2.ZERO)\
|
||||
.expand(stroke_end)\
|
||||
.grow(brushsize * 0.5 + 1)
|
||||
rect = Rect2i(rect.position / chunk_size, rect.end / chunk_size)
|
||||
var key : Vector2i
|
||||
var keyf : Vector2
|
||||
for i in rect.size.x + 1:
|
||||
for j in rect.size.y + 1:
|
||||
key = (rect.position + Vector2i(i, j)) * chunk_size
|
||||
keyf = Vector2(key)
|
||||
if !last_edits_chunks.has(key): continue
|
||||
|
||||
paint(
|
||||
last_edits_chunks[key],
|
||||
stroke_end - keyf,
|
||||
stroke_start - keyf,
|
||||
key,
|
||||
pressure
|
||||
)
|
||||
|
||||
|
||||
func paint(on_image : Image, stroke_start : Vector2, stroke_end : Vector2, chunk_position : Vector2i, pressure : float):
|
||||
var unsolid_radius := (brushsize * 0.5) * (1.0 - hardness)
|
||||
var radius := lerpf(brushminsize, brushsize, pressure if pen_flags[0] else 1.0) * 0.5
|
||||
var solid_radius := radius - unsolid_radius
|
||||
|
||||
var color : Color
|
||||
if brush_type == BRUSH_ERASE:
|
||||
color = Color.BLACK
|
||||
|
||||
elif pen_flags[2]:
|
||||
color = lerp(drawing_color2, drawing_color1, pressure)
|
||||
|
||||
else:
|
||||
color = drawing_color1
|
||||
|
||||
color.a *= opacity
|
||||
if pen_flags[1]:
|
||||
color.a *= pressure
|
||||
|
||||
var new_rect := Rect2i(stroke_start, Vector2i.ZERO)\
|
||||
.expand(stroke_end)\
|
||||
.grow(radius + 2)\
|
||||
.intersection(Rect2i(Vector2i.ZERO, on_image.get_size()))
|
||||
|
||||
if new_rect.size == Vector2i.ZERO:
|
||||
return
|
||||
|
||||
last_affected_rect = last_affected_rect\
|
||||
.expand(Vector2i(new_rect.position) + chunk_position)\
|
||||
.expand(Vector2i(new_rect.end) + chunk_position)
|
||||
|
||||
stroke_start = stroke_start.floor() + Vector2(0.5, 0.5)
|
||||
stroke_end = stroke_end.floor() + Vector2(0.5, 0.5)
|
||||
var cur_pos
|
||||
for i in new_rect.size.x:
|
||||
for j in new_rect.size.y:
|
||||
cur_pos = new_rect.position + Vector2i(i, j)
|
||||
if is_out_of_bounds(cur_pos + chunk_position, selection.get_size()):
|
||||
continue
|
||||
|
||||
if !selection.get_bitv(cur_pos + chunk_position):
|
||||
continue
|
||||
|
||||
on_image.set_pixelv(cur_pos, get_new_pixel(
|
||||
on_image, color,
|
||||
stroke_start, stroke_end, Vector2(cur_pos) + brush_offset,
|
||||
radius, solid_radius
|
||||
))
|
||||
|
||||
|
||||
func get_new_pixel(on_image : Image, color : Color, stroke_start : Vector2, stroke_end : Vector2, cur_pos : Vector2, radius : float, solid_radius : float):
|
||||
var old_color = on_image.get_pixelv(cur_pos)
|
||||
var distance = Geometry2D.get_closest_point_to_segment(
|
||||
cur_pos, stroke_start, stroke_end
|
||||
).distance_to(cur_pos)
|
||||
|
||||
if distance <= solid_radius:
|
||||
var blended = old_color.blend(color)
|
||||
blended.a = max(old_color.a, color.a)
|
||||
return blended
|
||||
|
||||
elif distance <= radius:
|
||||
var blended := old_color.blend(color)
|
||||
distance = (distance - solid_radius) / (radius - solid_radius)
|
||||
# Possible better handling of variable pressure,
|
||||
# but creates artifacts when zig-zagging
|
||||
blended.a = max(old_color.a, color.a * (1.0 - distance * distance))
|
||||
# This one also creates artifacts, but this is during normal brush usage.
|
||||
# blended.a = lerp(old_color.a, color.a, (1.0 - distance * distance))
|
||||
return blended
|
||||
|
||||
else:
|
||||
return old_color
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if drawing:
|
||||
for k in last_edits_chunks:
|
||||
last_edits_textures[k].update(last_edits_chunks[k])
|
||||
image_view.draw_texture(last_edits_textures[k], k)
|
||||
|
||||
var circle_center = Vector2(mouse_position + Vector2i.ONE) - brush_offset
|
||||
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 0.1, PI * 0.9, 32, crosshair_color, 1.0)
|
||||
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 1.1, PI * 1.9, 32, crosshair_color, 1.0)
|
||||
# With region set to (0, 0, 0, 0), hides the image.
|
||||
# image_view.region_enabled = drawing
|
81
addons/sprite_painter/src/editing_tools/tool_brush_clone.gd
Normal file
@ -0,0 +1,81 @@
|
||||
@tool
|
||||
extends "res://addons/sprite_painter/src/editing_tools/tool_brush.gd"
|
||||
|
||||
@export var clone_preview_color := Color(0.5, 0.5, 0.5, 0.75)
|
||||
|
||||
var replace_alpha := false
|
||||
|
||||
var clone_offset := Vector2.ZERO
|
||||
var clone_offset_editing := false
|
||||
var source_image : Image
|
||||
var clone_offset_view : Control
|
||||
|
||||
|
||||
func _ready():
|
||||
super._ready()
|
||||
clone_offset_view = add_text_display()
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
source_image = image
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
clone_offset_editing = event.pressed
|
||||
clone_offset = clone_offset.floor()
|
||||
|
||||
else:
|
||||
super.mouse_pressed(event, image, color1, color2)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if clone_offset_editing:
|
||||
clone_offset -= event.relative
|
||||
clone_offset_view.text = "Offset: " + str(clone_offset.floor())
|
||||
|
||||
super.mouse_moved(event)
|
||||
|
||||
|
||||
func get_new_pixel(on_image : Image, color : Color, stroke_start : Vector2, stroke_end : Vector2, cur_pos : Vector2, radius : float, solid_radius : float):
|
||||
var old_color := on_image.get_pixelv(cur_pos)
|
||||
var cloned_color := source_image.get_pixel(
|
||||
fposmod(cur_pos.x + clone_offset.x, source_image.get_width()),
|
||||
fposmod(cur_pos.y + clone_offset.y, source_image.get_height())
|
||||
)
|
||||
var distance := Geometry2D.get_closest_point_to_segment(
|
||||
cur_pos, stroke_start, stroke_end
|
||||
).distance_to(cur_pos)
|
||||
|
||||
if distance <= solid_radius:
|
||||
var blended = old_color.blend(cloned_color)
|
||||
blended.a = max(old_color.a, cloned_color.a)
|
||||
return blended
|
||||
|
||||
elif distance <= radius:
|
||||
var blended = old_color.blend(cloned_color)
|
||||
distance = (distance - solid_radius) / (radius - solid_radius)
|
||||
# blended.a = max(old_color.a, cloned_color.a * (1.0 - distance * distance))
|
||||
blended.a = lerp(old_color.a, cloned_color.a, (1.0 - distance * distance))
|
||||
return blended
|
||||
|
||||
else:
|
||||
return old_color
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
super.draw_preview(image_view, mouse_position)
|
||||
|
||||
if clone_offset == Vector2.ZERO:
|
||||
image_view.draw_string(
|
||||
get_theme_font("main", "EditorFonts"),
|
||||
mouse_position,
|
||||
"Hold Right Mouse and drag to change offset.",
|
||||
HORIZONTAL_ALIGNMENT_CENTER
|
||||
)
|
||||
|
||||
var circle_center := Vector2(mouse_position + Vector2i.ONE + Vector2i(clone_offset)) - brush_offset
|
||||
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 0.6, PI * 1.4, 32, clone_preview_color, 1.0)
|
||||
image_view.draw_arc(circle_center, brushsize * 0.5 + 0.5, PI * 1.6, PI * 2.4, 32, clone_preview_color, 1.0)
|
@ -0,0 +1,78 @@
|
||||
@tool
|
||||
extends "res://addons/sprite_painter/src/editing_tools/tool_brush.gd"
|
||||
|
||||
@export var tilt_preview_scale := 4.0
|
||||
|
||||
var shrink := 1.0
|
||||
|
||||
var mouse_tilt_editing := false
|
||||
var mouse_tilt := Vector2.ZERO
|
||||
|
||||
var tilt : Vector2
|
||||
var tilt_length : float
|
||||
|
||||
|
||||
func _ready():
|
||||
super._ready()
|
||||
add_property("Edge Shrink", shrink * 100,
|
||||
func (x): shrink = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100]
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
mouse_tilt_editing = event.pressed
|
||||
mouse_tilt = mouse_tilt.limit_length(1.0)
|
||||
|
||||
else:
|
||||
super.mouse_pressed(event, image, color1, color2)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if mouse_tilt_editing:
|
||||
mouse_tilt += event.relative / (brushsize * tilt_preview_scale)
|
||||
tilt = mouse_tilt.limit_length(1.0)
|
||||
return
|
||||
|
||||
if event.tilt != Vector2.ZERO:
|
||||
tilt = Vector2(event.tilt.x, -event.tilt.y)
|
||||
|
||||
var joy_tilt = Vector2(Input.get_joy_axis(0, JOY_AXIS_LEFT_X), Input.get_joy_axis(0, JOY_AXIS_LEFT_Y))
|
||||
if joy_tilt != Vector2.ZERO:
|
||||
tilt = joy_tilt
|
||||
|
||||
tilt_length = tilt.length() * 0.707214
|
||||
var tilt_basis = Basis.from_euler(Vector3(tilt.x * PI * 0.499, tilt.y * PI * 0.499, 0.0))
|
||||
var normal = Vector3.BACK * tilt_basis
|
||||
drawing_color1 = Color(normal.y * 0.5 + 0.5, normal.x * 0.5 + 0.5, normal.z, 1.0)
|
||||
|
||||
super.mouse_moved(event)
|
||||
|
||||
|
||||
func get_new_pixel(on_image, color, stroke_start, stroke_end, cur_pos, radius, solid_radius):
|
||||
return super.get_new_pixel(
|
||||
on_image, on_image.get_pixelv(cur_pos).blend(Color(color, color.a)), stroke_start, stroke_end, cur_pos,
|
||||
radius * (1.0 - tilt_length * shrink), solid_radius * (1.0 - tilt_length * shrink)
|
||||
)
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
var circle_center = Vector2(mouse_position + Vector2i.ONE) - brush_offset
|
||||
image_view.draw_line(
|
||||
circle_center,
|
||||
circle_center + tilt * brushsize * tilt_preview_scale,
|
||||
crosshair_color,
|
||||
1.01
|
||||
)
|
||||
if mouse_tilt_editing:
|
||||
image_view.draw_arc(circle_center - mouse_tilt * brushsize * tilt_preview_scale, brushsize * tilt_preview_scale + 0.5, PI * 0.6, PI * 1.4, 32, crosshair_color, 1.0)
|
||||
image_view.draw_arc(circle_center - mouse_tilt * brushsize * tilt_preview_scale, brushsize * tilt_preview_scale + 0.5, PI * 1.6, PI * 2.4, 32, crosshair_color, 1.0)
|
||||
|
||||
super.draw_preview(image_view, mouse_position)
|
@ -0,0 +1,46 @@
|
||||
@tool
|
||||
extends "res://addons/sprite_painter/src/editing_tools/tool_brush.gd"
|
||||
|
||||
var fill_mode := 0
|
||||
var tolerance := 0.0
|
||||
|
||||
var allowed_pixels = BitMap.new()
|
||||
var image_size := Vector2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
super._ready()
|
||||
add_property("Fill Mode", fill_mode,
|
||||
func (x): fill_mode = x,
|
||||
TOOL_PROP_ENUM,
|
||||
[&"Contiguous", &"Global"]
|
||||
)
|
||||
add_property("Tolerance", tolerance * 100,
|
||||
func (x): tolerance = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100]
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
image_size = image.get_size()
|
||||
if event.pressed:
|
||||
allowed_pixels.create(image.get_size())
|
||||
ImageFillTools.fill_on_image(
|
||||
image, allowed_pixels, event.position,
|
||||
tolerance, fill_mode == 0, selection
|
||||
)
|
||||
|
||||
super.mouse_pressed(event, image, color1, color2)
|
||||
|
||||
|
||||
func get_new_pixel(on_image : Image, color : Color, stroke_start : Vector2, stroke_end : Vector2, cur_pos : Vector2, radius : float, solid_radius : float):
|
||||
if !allowed_pixels.get_bitv(cur_pos):
|
||||
return on_image.get_pixelv(cur_pos)
|
||||
|
||||
return super.get_new_pixel(on_image, color, stroke_start, stroke_end, cur_pos, radius, solid_radius)
|
85
addons/sprite_painter/src/editing_tools/tool_bucket.gd
Normal file
@ -0,0 +1,85 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
var fill_mode := 0
|
||||
var tolerance := 0.0
|
||||
|
||||
var drawing := false
|
||||
var drawing_color := Color.BLACK
|
||||
var image : Image
|
||||
var start_color := Color.TRANSPARENT
|
||||
var affected_pixels := BitMap.new()
|
||||
var last_affected_rect := Rect2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_property("Fill Mode", fill_mode,
|
||||
func (x): fill_mode = x,
|
||||
TOOL_PROP_ENUM,
|
||||
[&"Contiguous", &"Global"]
|
||||
)
|
||||
add_property("Tolerance", tolerance * 100,
|
||||
func (x): tolerance = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100],
|
||||
true
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
drawing = event.pressed
|
||||
drawing_color = Color.BLACK.blend(color1)
|
||||
start_color = image.get_pixelv(event.position)
|
||||
self.image = image
|
||||
if drawing:
|
||||
affected_pixels.create(image.get_size())
|
||||
fill(event.position)
|
||||
|
||||
else:
|
||||
for i in image.get_width():
|
||||
for j in image.get_height():
|
||||
if affected_pixels.get_bit(i, j):
|
||||
set_image_pixel(image, i, j, color1)
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
return last_affected_rect.grow_individual(0, 0, 1, 1)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if !drawing: return
|
||||
if is_out_of_bounds(event.position, image.get_size()):
|
||||
affected_pixels.create(affected_pixels.get_size())
|
||||
return
|
||||
|
||||
var cur_color := image.get_pixelv(event.position)
|
||||
if cur_color == start_color:
|
||||
return
|
||||
|
||||
start_color = cur_color
|
||||
fill(event.position)
|
||||
|
||||
|
||||
func fill(start_pos : Vector2):
|
||||
last_affected_rect = ImageFillTools.fill_on_image(
|
||||
image,
|
||||
affected_pixels,
|
||||
start_pos,
|
||||
tolerance,
|
||||
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT),
|
||||
selection
|
||||
)
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if !drawing: return
|
||||
|
||||
image_view.draw_rect(Rect2(mouse_position, Vector2.ONE), Color.WHITE)
|
||||
ImageFillTools.draw_bitmap(image_view, affected_pixels, drawing_color)
|
186
addons/sprite_painter/src/editing_tools/tool_gradient.gd
Normal file
@ -0,0 +1,186 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
enum {
|
||||
GRADIENT_LINEAR,
|
||||
GRADIENT_LINEAR_MIRRORED,
|
||||
GRADIENT_RADIAL,
|
||||
GRADIENT_CONIC,
|
||||
GRADIENT_BOUNDING_BOX,
|
||||
}
|
||||
|
||||
@export var line_color = Color.GRAY
|
||||
@export var line_aligned_color = Color.GREEN
|
||||
@export var point_grab_area = Vector2(5, 5)
|
||||
@export var point_grab_color = Color.GRAY
|
||||
@export var shader_preview : NodePath
|
||||
@export var shader_viewport_texture : Texture2D
|
||||
|
||||
var gradient_type := 0
|
||||
var fill_mode := 0
|
||||
var tolerance := 1.0
|
||||
|
||||
var drawing := false
|
||||
var default_gradient := Gradient.new()
|
||||
var custom_gradient : Gradient = null
|
||||
var default_color2 := Color.TRANSPARENT
|
||||
|
||||
var affected_pixels := BitMap.new()
|
||||
var affected_pixels_tex : ImageTexture
|
||||
var points := [Vector2(-INF, -INF), Vector2(-INF, -INF)]
|
||||
var point_grabbed := -1
|
||||
var last_affected_rect := Rect2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
default_gradient.add_point(1.0, Color.WHITE)
|
||||
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_property("Type", gradient_type,
|
||||
func (x): gradient_type = x,
|
||||
TOOL_PROP_ICON_ENUM,
|
||||
{
|
||||
"Line": "Linear",
|
||||
"Hsize": "Linear Mirrored",
|
||||
"Node": "Radial",
|
||||
"ToolRotate": "Conic",
|
||||
"Groups": "Bounding Box",
|
||||
},
|
||||
true
|
||||
)
|
||||
add_property("Gradient", custom_gradient,
|
||||
func (x): custom_gradient = x,
|
||||
TOOL_PROP_RESOURCE,
|
||||
["Gradient"]
|
||||
)
|
||||
add_property("Fill Mode", fill_mode,
|
||||
func (x): fill_mode = x,
|
||||
TOOL_PROP_ENUM,
|
||||
[&"Contiguous", &"Global"]
|
||||
)
|
||||
add_property("Tolerance", tolerance * 100,
|
||||
func (x): tolerance = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100]
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
drawing = event.pressed
|
||||
default_gradient.set_color(0, color1)
|
||||
default_gradient.set_color(1, color2)
|
||||
var cur_color := image.get_pixelv(event.position)
|
||||
if drawing:
|
||||
if try_grab_point(event):
|
||||
return
|
||||
|
||||
if is_out_of_bounds(event.position, image.get_size()):
|
||||
affected_pixels.create(affected_pixels.get_size())
|
||||
return
|
||||
|
||||
points[0] = event.position.floor()
|
||||
points[1] = event.position.floor()
|
||||
affected_pixels.create(image.get_size())
|
||||
fill(event.position, image)
|
||||
var mask = affected_pixels.convert_to_image()
|
||||
mask.clear_mipmaps()
|
||||
affected_pixels_tex = ImageTexture.create_from_image(mask)
|
||||
update_preview_shader()
|
||||
point_grabbed = 1
|
||||
|
||||
else:
|
||||
point_grabbed = -1
|
||||
if points[0] == points[1]:
|
||||
points[0] = Vector2(-INF, -INF)
|
||||
points[1] = Vector2(-INF, -INF)
|
||||
return
|
||||
|
||||
var result_image = shader_viewport_texture.get_image()
|
||||
for i in result_image.get_width():
|
||||
for j in result_image.get_height():
|
||||
if affected_pixels.get_bit(i, j):
|
||||
set_image_pixel(image, i, j, result_image.get_pixel(i, j))
|
||||
|
||||
|
||||
func try_grab_point(event : InputEventMouse) -> bool:
|
||||
for i in points.size():
|
||||
if Rect2(points[i] - point_grab_area * 0.5, point_grab_area).has_point(event.position):
|
||||
point_grabbed = i
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
return last_affected_rect.grow_individual(0, 0, 1, 1)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if point_grabbed == -1: return
|
||||
|
||||
if Input.is_key_pressed(KEY_SHIFT):
|
||||
var origin = points[1 - point_grabbed]
|
||||
var angle_rounded = snappedf(origin.angle_to_point(event.position), PI * 0.25)
|
||||
var distance = (event.position - origin).length()
|
||||
points[point_grabbed] = (origin + Vector2(
|
||||
distance * cos(angle_rounded),
|
||||
distance * sin(angle_rounded)
|
||||
)).floor()
|
||||
|
||||
else:
|
||||
points[point_grabbed] = event.position.floor()
|
||||
|
||||
update_preview_shader()
|
||||
|
||||
|
||||
func update_preview_shader():
|
||||
var preview_node = get_node(shader_preview)
|
||||
var g_tex = preview_node.material.get_shader_parameter("gradient")
|
||||
g_tex.gradient = default_gradient if custom_gradient == null else custom_gradient
|
||||
preview_node.texture = affected_pixels_tex
|
||||
preview_shader.set_shader_parameter("gradient", g_tex)
|
||||
preview_shader.set_shader_parameter("type", gradient_type)
|
||||
preview_shader.set_shader_parameter("from", points[0] / Vector2(affected_pixels.get_size()))
|
||||
preview_shader.set_shader_parameter("delta", (points[1] - points[0]) / Vector2(affected_pixels.get_size()))
|
||||
|
||||
|
||||
func fill(start_pos : Vector2, image : Image):
|
||||
last_affected_rect = ImageFillTools.fill_on_image(
|
||||
image,
|
||||
affected_pixels,
|
||||
start_pos,
|
||||
tolerance,
|
||||
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT),
|
||||
selection
|
||||
)
|
||||
|
||||
|
||||
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if points[0] == Vector2(-INF, -INF):
|
||||
image_view.hide()
|
||||
return
|
||||
|
||||
image_view.texture = affected_pixels_tex
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if points[0] == Vector2(-INF, -INF):
|
||||
return
|
||||
|
||||
for i in points.size():
|
||||
image_view.draw_line(points[0], points[1], line_color, 1.1)
|
||||
image_view.draw_rect(
|
||||
Rect2(points[i] - point_grab_area * 0.5, point_grab_area),
|
||||
point_grab_color, false, 2
|
||||
)
|
||||
|
||||
|
||||
func _on_visibility_changed():
|
||||
points = [Vector2(-INF, -INF), Vector2(-INF, -INF)]
|
||||
super._on_visibility_changed()
|
49
addons/sprite_painter/src/editing_tools/tool_line.gd
Normal file
@ -0,0 +1,49 @@
|
||||
@tool
|
||||
extends "./tool_shape.gd"
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_property("Width", line_width,
|
||||
func (x): line_width = x,
|
||||
TOOL_PROP_INT,
|
||||
[0, 250]
|
||||
)
|
||||
add_property("Flags", [erase_mode, aa],
|
||||
func (x):
|
||||
print(x)
|
||||
erase_mode = x[0]
|
||||
aa = x[1],
|
||||
TOOL_PROP_ICON_FLAGS,
|
||||
{"Eraser" : "Erase Mode", "CurveTexture" : "Anti-Aliasing"}
|
||||
)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if !drawing: return
|
||||
if Input.is_key_pressed(KEY_SHIFT):
|
||||
var angle_rounded = snappedf((Vector2(start_pos)).angle_to_point(event.position), PI * 0.25)
|
||||
var distance = (event.position - Vector2(start_pos)).length()
|
||||
end_pos = (Vector2(start_pos) + Vector2(
|
||||
distance * cos(angle_rounded),
|
||||
distance * sin(angle_rounded)
|
||||
)).round()
|
||||
|
||||
else:
|
||||
end_pos = event.position.floor()
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
return super.get_affected_rect().grow(line_width)
|
||||
|
||||
|
||||
func update_preview_cheddar():
|
||||
var rect = super.get_affected_rect()
|
||||
preview_shader.set_shader_parameter("color", color_line if !erase_mode else Color.BLACK.blend(color_line));
|
||||
preview_shader.set_shader_parameter("origin", Vector2(start_pos) + Vector2(0.5, 0.5));
|
||||
preview_shader.set_shader_parameter("width", line_width);
|
||||
preview_shader.set_shader_parameter("enable_aa", aa);
|
||||
|
||||
if end_pos == start_pos: return # The SDF for this case evaluates to being inside; abort
|
||||
preview_shader.set_shader_parameter("delta", end_pos - start_pos);
|
98
addons/sprite_painter/src/editing_tools/tool_pencil.gd
Normal file
@ -0,0 +1,98 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
@export var crosshair_color := Color(0.5, 0.5, 0.5, 0.75)
|
||||
@export var crosshair_size := 3
|
||||
@export var crosshair_size_ruler := 32
|
||||
|
||||
var ruler_mode := false
|
||||
var jaggies_removal := true
|
||||
|
||||
var drawing := false
|
||||
var drawing_color := Color()
|
||||
var drawing_positions : Array[Vector2]
|
||||
var image_size := Vector2()
|
||||
var last_affected_rect := Rect2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_property("Guide Lines", ruler_mode,
|
||||
func (x): ruler_mode = x,
|
||||
TOOL_PROP_BOOL,
|
||||
null,
|
||||
true
|
||||
)
|
||||
add_property("Remove Jaggies", jaggies_removal,
|
||||
func (x): jaggies_removal = x,
|
||||
TOOL_PROP_BOOL
|
||||
)
|
||||
drawing_positions = []
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
drawing = event.pressed
|
||||
drawing_color = Color.BLACK.blend(color1)
|
||||
image_size = image.get_size()
|
||||
if drawing:
|
||||
drawing_positions.clear()
|
||||
last_affected_rect = Rect2i(event.position, Vector2i.ZERO)
|
||||
_add_point(event.position)
|
||||
|
||||
else:
|
||||
for x in drawing_positions:
|
||||
set_image_pixelv(image, x, color1)
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
return last_affected_rect.grow_individual(0, 0, 1, 1)
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if !drawing: return
|
||||
var pt_count = max(abs(event.relative.x), abs(event.relative.y))
|
||||
var lerp_step = 1 / pt_count
|
||||
for i in pt_count:
|
||||
_add_point(event.position + Vector2.ONE - event.relative * i * lerp_step - Vector2.ONE)
|
||||
|
||||
|
||||
func _add_point(pt : Vector2):
|
||||
pt = pt.floor()
|
||||
if drawing_positions.size() >= 1 && drawing_positions[-1] == pt:
|
||||
return
|
||||
|
||||
if pt.x < 0 || pt.y < 0 || pt.x >= image_size.x || pt.y >= image_size.y:
|
||||
return
|
||||
|
||||
if jaggies_removal && drawing_positions.size() > 2:
|
||||
var diff1 = (drawing_positions[-1] - drawing_positions[-2]).abs()
|
||||
var diff2 = (pt - drawing_positions[-1]).abs()
|
||||
if diff1 != diff2 && diff1.x + diff1.y + diff2.x + diff2.y == 2:
|
||||
drawing_positions.pop_back()
|
||||
|
||||
drawing_positions.append(pt)
|
||||
last_affected_rect = last_affected_rect.expand(pt)
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if drawing:
|
||||
for x in drawing_positions:
|
||||
image_view.draw_rect(Rect2(x, Vector2.ONE), drawing_color)
|
||||
|
||||
if !ruler_mode:
|
||||
draw_crosshair(image_view, mouse_position, crosshair_size, crosshair_color)
|
||||
|
||||
else:
|
||||
draw_crosshair(image_view, mouse_position, crosshair_size_ruler, crosshair_color)
|
||||
var diag_distance := 8
|
||||
var posf := Vector2(mouse_position) + Vector2(0.5, 0.5)
|
||||
image_view.draw_line(posf + Vector2(-1, +1) * diag_distance, posf + Vector2(-1, +1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
|
||||
image_view.draw_line(posf + Vector2(+1, -1) * diag_distance, posf + Vector2(+1, -1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
|
||||
image_view.draw_line(posf + Vector2(+1, +1) * diag_distance, posf + Vector2(+1, +1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
|
||||
image_view.draw_line(posf + Vector2(-1, -1) * diag_distance, posf + Vector2(-1, -1) * (diag_distance + crosshair_size_ruler), crosshair_color, 1)
|
138
addons/sprite_painter/src/editing_tools/tool_scripting.gd
Normal file
@ -0,0 +1,138 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
@export var workspace : NodePath
|
||||
|
||||
var live_update := true
|
||||
|
||||
var timer : Timer
|
||||
var param_grid : Control
|
||||
var script_instance : ImageScript
|
||||
var original_image : Image
|
||||
var result_image_tex : ImageTexture
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
var button = add_property("Script",
|
||||
"",
|
||||
load_script,
|
||||
TOOL_PROP_FOLDER_SCAN,
|
||||
"res://addons/sprite_painter/image_scripts"
|
||||
)
|
||||
button.text = "Choose script... "
|
||||
button.fit_to_longest_item = false
|
||||
param_grid = start_property_grid()
|
||||
add_separator()
|
||||
add_property("Live Update",
|
||||
live_update,
|
||||
func(x):
|
||||
live_update = x
|
||||
if x: update_script(true),
|
||||
TOOL_PROP_BOOL
|
||||
)
|
||||
|
||||
var buttons = add_button_panel(["Apply", "Reset"]).get_children()
|
||||
buttons[0].pressed.connect(func():
|
||||
var ws = get_node(workspace)
|
||||
var old_image = ws.edited_image
|
||||
ws.replace_image(old_image, result_image_tex.get_image())
|
||||
ws.image_replaced.emit(old_image, ws.edited_image)
|
||||
)
|
||||
buttons[1].pressed.connect(func(): load_script(script_instance.get_script()))
|
||||
|
||||
# Updates are expensive if images are changed on the CPU.
|
||||
# Update only sometimes to reduce lag
|
||||
timer = Timer.new()
|
||||
timer.wait_time = 2.0
|
||||
timer.one_shot = true
|
||||
timer.timeout.connect(_on_timer_timeout)
|
||||
add_child(timer)
|
||||
|
||||
|
||||
func load_script(script : Script):
|
||||
script_instance = script.new()
|
||||
param_grid.free()
|
||||
param_grid = start_property_grid()
|
||||
param_grid.get_parent().move_child(param_grid, 3)
|
||||
param_grid.get_parent().get_child(2).hide()
|
||||
|
||||
for x in script_instance._get_param_list():
|
||||
add_property(
|
||||
x[0], # Name
|
||||
x[2], # Value
|
||||
func(y):
|
||||
script_instance._params[x[0]] = y
|
||||
update_script()
|
||||
if y is Resource && !y.changed.is_connected(update_script):
|
||||
y.changed.connect(update_script),
|
||||
x[1], # Type
|
||||
x[3] if x.size() > 3 else null # Hint (if any)
|
||||
)
|
||||
script_instance._params[x[0]] = x[2]
|
||||
if x[2] is Resource && !x[2].changed.is_connected(update_script):
|
||||
x[2].changed.connect(update_script)
|
||||
|
||||
script_instance._ready(original_image)
|
||||
update_script()
|
||||
|
||||
|
||||
func update_script(automatic : bool = false):
|
||||
if !live_update: return
|
||||
if timer.time_left != 0.0: return
|
||||
|
||||
var new_image = Image.create_from_data(
|
||||
original_image.get_width(),
|
||||
original_image.get_height(),
|
||||
false,
|
||||
original_image.get_format(),
|
||||
original_image.get_data()
|
||||
)
|
||||
result_image_tex = ImageTexture.create_from_image(
|
||||
script_instance._get_image(new_image, selection)
|
||||
)
|
||||
if !automatic:
|
||||
timer.start()
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
original_image = image
|
||||
|
||||
|
||||
func get_affected_rect() -> Rect2i:
|
||||
return Rect2i()
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
original_image = get_node(workspace).edited_image
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
pass
|
||||
|
||||
|
||||
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
image_view.texture = result_image_tex
|
||||
|
||||
|
||||
func _on_visibility_changed():
|
||||
super._on_visibility_changed()
|
||||
if is_visible_in_tree() && script_instance != null:
|
||||
script_instance._ready(original_image)
|
||||
update_script(true)
|
||||
|
||||
|
||||
func _on_timer_timeout():
|
||||
update_script(true)
|
||||
|
||||
|
||||
func _on_workspace_image_replaced(old_image, new_image):
|
||||
original_image = new_image
|
||||
if is_visible_in_tree() && script_instance != null:
|
||||
script_instance._ready(original_image)
|
||||
update_script(true)
|
219
addons/sprite_painter/src/editing_tools/tool_select.gd
Normal file
@ -0,0 +1,219 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
@export var preview_color := Color("1e90ff7f")
|
||||
@export var crosshair_size := 3
|
||||
|
||||
enum {
|
||||
DRAG_LEAVE_TRANSPARENT,
|
||||
DRAG_LEAVE_SECONDARY,
|
||||
DRAG_SELECTION_ONLY,
|
||||
DRAG_NONE,
|
||||
}
|
||||
|
||||
var operation := 0
|
||||
var dragging_mode := DRAG_LEAVE_TRANSPARENT
|
||||
|
||||
var mouse_down := false
|
||||
var selection_dragging := false
|
||||
var image : Image
|
||||
var draw_start := Vector2i()
|
||||
var draw_end := Vector2i()
|
||||
var image_size := Vector2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_selection_common_properties()
|
||||
add_selection_button_panel()
|
||||
|
||||
|
||||
func add_selection_common_properties():
|
||||
add_property("Operation", operation,
|
||||
func (x): operation = x,
|
||||
TOOL_PROP_ENUM,
|
||||
["Replace", "Add (Ctrl)", "Subtract (Right-click)", "Intersection", "Invert"],
|
||||
true
|
||||
)
|
||||
add_property("Drag Mode", dragging_mode,
|
||||
func (x): dragging_mode = x,
|
||||
TOOL_PROP_ENUM,
|
||||
["Transparent BG", "Secondary Color BG", "Move Selection Only", "Never Drag"]
|
||||
)
|
||||
|
||||
|
||||
func add_selection_button_panel():
|
||||
var selection_view = $"%SelectionView"
|
||||
var workspace = $"%Workspace"
|
||||
add_separator()
|
||||
var buttons = add_button_panel(
|
||||
["Deselect", "Erase", "Invert"]
|
||||
).get_children()
|
||||
buttons[0].pressed.connect(func():
|
||||
selection.set_bit_rect(Rect2i(Vector2i.ZERO, selection.get_size()), true)
|
||||
selection_view.queue_redraw()
|
||||
)
|
||||
|
||||
# This fragment of code is worth framing IMO.
|
||||
buttons[1].pressed.connect(func():
|
||||
var bg_color = Color.TRANSPARENT
|
||||
if dragging_mode == DRAG_LEAVE_SECONDARY:
|
||||
bg_color = get_parent().current_color2
|
||||
|
||||
workspace.make_image_edit(
|
||||
func():
|
||||
for i in selection.get_size().x:
|
||||
for j in selection.get_size().y:
|
||||
if selection.get_bit(i, j):
|
||||
image.set_pixel(i, j, bg_color),
|
||||
Rect2i(Vector2i.ZERO, selection.get_size())
|
||||
)
|
||||
)
|
||||
|
||||
buttons[2].pressed.connect(func():
|
||||
for i in selection.get_size().x:
|
||||
for j in selection.get_size().y:
|
||||
selection.set_bit(i, j, !selection.get_bit(i, j))
|
||||
|
||||
selection_view.queue_redraw()
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
var subtract = Input.is_key_pressed(KEY_ALT) || event.button_index == MOUSE_BUTTON_RIGHT
|
||||
var add = Input.is_key_pressed(KEY_CTRL) || Input.is_key_pressed(KEY_META)
|
||||
self.image = image
|
||||
image_size = image.get_size()
|
||||
mouse_down = event.pressed
|
||||
if mouse_down:
|
||||
draw_start = event.position
|
||||
draw_end = draw_start
|
||||
selection_dragging = (
|
||||
dragging_mode != DRAG_NONE
|
||||
&& !subtract && !add
|
||||
&& selection.get_bitv(event.position)
|
||||
&& !is_selection_empty()
|
||||
)
|
||||
|
||||
elif selection_dragging:
|
||||
move_selected_pixels(
|
||||
image,
|
||||
Vector2i(draw_end - draw_start),
|
||||
add,
|
||||
color2
|
||||
)
|
||||
|
||||
else:
|
||||
apply_selection(add, subtract)
|
||||
|
||||
|
||||
func apply_selection(add_modifier, subtract_modifier):
|
||||
var rect = get_selection_rect()
|
||||
if operation == OPERATION_REPLACE || is_selection_empty():
|
||||
if !add_modifier && !subtract_modifier:
|
||||
selection.set_bit_rect(Rect2i(Vector2i.ZERO, image_size), false)
|
||||
|
||||
selection.set_bit_rect(rect, !subtract_modifier)
|
||||
|
||||
match operation:
|
||||
OPERATION_ADD:
|
||||
selection.set_bit_rect(rect, !subtract_modifier)
|
||||
|
||||
OPERATION_SUBTRACT:
|
||||
selection.set_bit_rect(rect, subtract_modifier)
|
||||
|
||||
OPERATION_INTERSECTION, OPERATION_XOR:
|
||||
var intersect = operation == OPERATION_INTERSECTION
|
||||
var result_bit
|
||||
for i in image_size.x:
|
||||
for j in image_size.y:
|
||||
result_bit = (
|
||||
selection.get_bit(i, j)
|
||||
&& rect.has_point(Vector2i(i, j))
|
||||
) if intersect else (
|
||||
selection.get_bit(i, j)
|
||||
!= rect.has_point(Vector2i(i, j))
|
||||
)
|
||||
selection.set_bit(i, j, result_bit != subtract_modifier)
|
||||
|
||||
|
||||
func move_selected_pixels(image, drag_vec, copy, back_color):
|
||||
var old_pixels = []
|
||||
var old_sel = BitMap.new()
|
||||
var move_image = dragging_mode != DRAG_SELECTION_ONLY
|
||||
old_sel.create(image_size)
|
||||
old_pixels.resize(image_size.x * image_size.y)
|
||||
|
||||
if dragging_mode == DRAG_LEAVE_TRANSPARENT:
|
||||
back_color = Color.TRANSPARENT
|
||||
|
||||
# Go through source pixels
|
||||
var source_selected := false
|
||||
for i in image_size.x:
|
||||
for j in image_size.y:
|
||||
source_selected = selection.get_bit(i, j)
|
||||
old_sel.set_bit(i, j, source_selected)
|
||||
selection.set_bit(i, j, false)
|
||||
old_pixels[i + j * image_size.x] = image.get_pixel(i, j)
|
||||
if !copy && source_selected && move_image:
|
||||
image.set_pixel(i, j, back_color)
|
||||
|
||||
# Paste into destination pixels
|
||||
var dest_pixel : Color
|
||||
var dest_pos : Vector2i
|
||||
for i in image_size.x:
|
||||
for j in image_size.y:
|
||||
dest_pos = Vector2i(i, j) - drag_vec
|
||||
if (is_out_of_bounds(dest_pos, image_size) || !old_sel.get_bitv(dest_pos)):
|
||||
continue
|
||||
|
||||
selection.set_bit(i, j, true)
|
||||
dest_pixel = old_pixels[dest_pos.x + dest_pos.y * image_size.x]
|
||||
if move_image:
|
||||
image.set_pixel(i, j, image.get_pixel(i, j).blend(dest_pixel))
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
if selection_dragging:
|
||||
# Can be anything!
|
||||
return Rect2i(Vector2i.ZERO, image_size)
|
||||
|
||||
else:
|
||||
return Rect2()
|
||||
|
||||
|
||||
func get_selection_rect():
|
||||
if draw_start == draw_end: return Rect2i()
|
||||
return get_rect_from_drag(draw_start, draw_end, Input.is_key_pressed(KEY_SHIFT))
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
draw_end = event.position
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if mouse_down:
|
||||
if selection_dragging:
|
||||
ImageFillTools.draw_bitmap(
|
||||
image_view,
|
||||
selection,
|
||||
preview_color,
|
||||
mouse_position - draw_start
|
||||
)
|
||||
|
||||
else:
|
||||
draw_selection_preview(image_view, mouse_position)
|
||||
|
||||
return
|
||||
|
||||
draw_crosshair(image_view, mouse_position, crosshair_size, preview_color)
|
||||
|
||||
|
||||
func draw_selection_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
image_view.draw_rect(get_selection_rect(), preview_color)
|
155
addons/sprite_painter/src/editing_tools/tool_shape.gd
Normal file
@ -0,0 +1,155 @@
|
||||
@tool
|
||||
extends EditingTool
|
||||
|
||||
enum {
|
||||
SHAPE_RECTANGLE,
|
||||
SHAPE_ELLIPSE,
|
||||
SHAPE_TRIANGLE,
|
||||
SHAPE_DIAMOND,
|
||||
SHAPE_HEXAGON,
|
||||
}
|
||||
|
||||
const I_SIN120 = sin(PI * 0.66666) # Used for making hexagons have equal qdges
|
||||
|
||||
@export var crosshair_color := Color(0.5, 0.5, 0.5, 0.75)
|
||||
@export var crosshair_size := 3
|
||||
@export var shader_viewport_texture : Texture2D
|
||||
|
||||
var shape := 0
|
||||
var fill_mode := 0
|
||||
var line_width := 1
|
||||
var erase_mode := false
|
||||
var aa := false
|
||||
|
||||
var drawing := false
|
||||
var color_line : Color = Color.BLACK
|
||||
var color_fill : Color = Color.WHITE
|
||||
var image_size := Vector2()
|
||||
|
||||
var start_pos := Vector2i()
|
||||
var end_pos := Vector2i()
|
||||
|
||||
|
||||
func _ready():
|
||||
var icon_folder = "res://addons/sprite_painter/graphics/"
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_property("Shape", shape,
|
||||
func (x): shape = x,
|
||||
TOOL_PROP_ICON_ENUM,
|
||||
{
|
||||
load(icon_folder + "rect_shape_2d.svg") : "Rectangle",
|
||||
load(icon_folder + "circle_shape_2d.svg") : "Ellipse",
|
||||
load(icon_folder + "triangle_shape_2d.svg") : "Triangle",
|
||||
load(icon_folder + "diamond_shape_2d.svg") : "Diamond",
|
||||
load(icon_folder + "hex_shape_2d.svg") : "Hexagon",
|
||||
},
|
||||
true
|
||||
)
|
||||
add_property("Fill Color", fill_mode,
|
||||
func (x): fill_mode = x,
|
||||
TOOL_PROP_ENUM,
|
||||
["Primary", "Secondary", "None (outline only)"]
|
||||
)
|
||||
add_property("Border Width", line_width,
|
||||
func (x): line_width = x,
|
||||
TOOL_PROP_INT,
|
||||
[0, 20]
|
||||
)
|
||||
add_property("Flags", [erase_mode, aa],
|
||||
func (x):
|
||||
erase_mode = x[0]
|
||||
aa = x[1],
|
||||
TOOL_PROP_ICON_FLAGS,
|
||||
{"Eraser" : "Erase Mode", "CurveTexture" : "Anti-Aliasing"}
|
||||
)
|
||||
|
||||
|
||||
func mouse_pressed(
|
||||
event : InputEventMouseButton,
|
||||
image : Image,
|
||||
color1 : Color = Color.BLACK,
|
||||
color2 : Color = Color.WHITE,
|
||||
):
|
||||
drawing = event.pressed
|
||||
color_line = color1
|
||||
color_fill = [color1, color2, Color.TRANSPARENT][fill_mode]
|
||||
image_size = image.get_size()
|
||||
if drawing:
|
||||
start_pos = Vector2i(event.position)
|
||||
end_pos = Vector2i(event.position)
|
||||
|
||||
else:
|
||||
var rect = get_affected_rect()
|
||||
var new_image = shader_viewport_texture.get_image()
|
||||
var cur_pos : Vector2i
|
||||
var cur_pixel : Color
|
||||
for i in rect.size.x:
|
||||
for j in rect.size.y:
|
||||
cur_pos = rect.position + Vector2i(i, j)
|
||||
if is_out_of_bounds(cur_pos, image_size):
|
||||
continue
|
||||
|
||||
cur_pixel = image.get_pixelv(cur_pos)
|
||||
if !erase_mode:
|
||||
set_image_pixelv(image, cur_pos,
|
||||
cur_pixel.blend(new_image.get_pixelv(cur_pos))
|
||||
)
|
||||
|
||||
else:
|
||||
set_image_pixelv(image, cur_pos, Color(
|
||||
cur_pixel,
|
||||
(cur_pixel.a - new_image.get_pixelv(cur_pos).a)
|
||||
))
|
||||
|
||||
|
||||
func get_affected_rect():
|
||||
var squarify = Input.is_key_pressed(KEY_SHIFT)
|
||||
var rect = get_rect_from_drag(start_pos, end_pos, squarify)
|
||||
if shape == SHAPE_HEXAGON && squarify:
|
||||
if (start_pos.x < end_pos.x) == (start_pos.y < end_pos.y):
|
||||
rect.size.y *= I_SIN120
|
||||
|
||||
else:
|
||||
rect.size.x *= I_SIN120
|
||||
|
||||
return rect
|
||||
|
||||
|
||||
func mouse_moved(event : InputEventMouseMotion):
|
||||
if !drawing: return
|
||||
end_pos = event.position
|
||||
|
||||
|
||||
func update_preview_cheddar():
|
||||
var rect = get_affected_rect()
|
||||
preview_shader.set_shader_parameter("shape_index", shape);
|
||||
preview_shader.set_shader_parameter("color_border", color_line if !erase_mode else Color.BLACK.blend(color_line));
|
||||
preview_shader.set_shader_parameter("color_fill", color_fill);
|
||||
preview_shader.set_shader_parameter("origin", rect.position);
|
||||
preview_shader.set_shader_parameter("shape_size", rect.size);
|
||||
preview_shader.set_shader_parameter("border_width", line_width);
|
||||
preview_shader.set_shader_parameter("drag_delta", start_pos - end_pos);
|
||||
preview_shader.set_shader_parameter("enable_aa", aa);
|
||||
|
||||
|
||||
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
image_view.texture = null
|
||||
if !drawing:
|
||||
image_view.hide()
|
||||
|
||||
else:
|
||||
if image_view.texture == null || image_view.texture.get_size() != image_size:
|
||||
image_view.texture = ImageTexture.create_from_image(Image.create(
|
||||
image_size.x,
|
||||
image_size.y,
|
||||
false,
|
||||
Image.FORMAT_L8 # Doesn't matter, won't read from it
|
||||
))
|
||||
|
||||
update_preview_cheddar()
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if !drawing:
|
||||
draw_crosshair(image_view, mouse_position, crosshair_size, crosshair_color)
|
73
addons/sprite_painter/src/editing_tools/tool_wand.gd
Normal file
@ -0,0 +1,73 @@
|
||||
@tool
|
||||
extends "./tool_select.gd"
|
||||
|
||||
var selection_operations = [
|
||||
func(s, d): return d,
|
||||
func(s, d): return s || d,
|
||||
func(s, d): return s && !d,
|
||||
func(s, d): return s && d,
|
||||
func(s, d): return s != d,
|
||||
]
|
||||
|
||||
var fill_mode := 0
|
||||
var tolerance := 0.0
|
||||
|
||||
|
||||
func _ready():
|
||||
add_name()
|
||||
start_property_grid()
|
||||
add_selection_common_properties()
|
||||
add_property("Fill Mode", fill_mode,
|
||||
func (x): fill_mode = x,
|
||||
TOOL_PROP_ENUM,
|
||||
[&"Contiguous", &"Global"]
|
||||
)
|
||||
add_property("Tolerance", tolerance * 100,
|
||||
func (x): tolerance = x * 0.01,
|
||||
TOOL_PROP_INT,
|
||||
[0, 100]
|
||||
)
|
||||
add_selection_button_panel()
|
||||
|
||||
|
||||
func apply_selection(add_modifier, subtract_modifier):
|
||||
var affected_pixels = BitMap.new()
|
||||
affected_pixels.create(image_size)
|
||||
var affected_rect = ImageFillTools.fill_on_image(
|
||||
image,
|
||||
affected_pixels,
|
||||
draw_end,
|
||||
tolerance,
|
||||
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT)
|
||||
)
|
||||
affected_rect.size += Vector2i.ONE
|
||||
var used_op_index = operation
|
||||
if operation == OPERATION_REPLACE || is_selection_empty():
|
||||
if !add_modifier && !subtract_modifier:
|
||||
selection.set_bit_rect(Rect2i(Vector2i.ZERO, image_size), false)
|
||||
|
||||
if add_modifier:
|
||||
used_op_index = OPERATION_ADD
|
||||
|
||||
var used_op = selection_operations[used_op_index]
|
||||
var cur_pos : Vector2i
|
||||
for i in affected_rect.size.x:
|
||||
for j in affected_rect.size.y:
|
||||
cur_pos = Vector2i(i, j) + affected_rect.position
|
||||
if !affected_pixels.get_bitv(cur_pos): continue
|
||||
selection.set_bitv(cur_pos, used_op.call(
|
||||
selection.get_bitv(cur_pos), true
|
||||
) != subtract_modifier)
|
||||
|
||||
|
||||
func draw_selection_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
var affected_pixels = BitMap.new()
|
||||
affected_pixels.create(image_size)
|
||||
var affected_rect = ImageFillTools.fill_on_image(
|
||||
image,
|
||||
affected_pixels,
|
||||
draw_end,
|
||||
tolerance,
|
||||
(fill_mode == 0) != Input.is_key_pressed(KEY_SHIFT)
|
||||
)
|
||||
ImageFillTools.draw_bitmap(image_view, affected_pixels, preview_color)
|
113
addons/sprite_painter/src/fill_tools.gd
Normal file
@ -0,0 +1,113 @@
|
||||
@tool
|
||||
class_name ImageFillTools
|
||||
|
||||
|
||||
static func fill_on_image(
|
||||
image : Image,
|
||||
result_into_mask : BitMap,
|
||||
start_pos : Vector2i,
|
||||
tolerance : float = 0.0,
|
||||
fill_contiguous : bool = true,
|
||||
selection : BitMap = null,
|
||||
) -> Rect2i:
|
||||
var affected_rect : Rect2i
|
||||
result_into_mask.create(image.get_size())
|
||||
if fill_contiguous:
|
||||
affected_rect = flood_fill(image, result_into_mask, start_pos, tolerance, selection)
|
||||
|
||||
else:
|
||||
affected_rect = global_fill(image, result_into_mask, start_pos, tolerance)
|
||||
|
||||
return affected_rect
|
||||
|
||||
|
||||
static func flood_fill(
|
||||
image : Image,
|
||||
result_into_mask : BitMap,
|
||||
start_pos : Vector2i,
|
||||
tolerance : float = 0.0,
|
||||
selection : BitMap = null
|
||||
) -> Rect2i:
|
||||
var start_color = image.get_pixelv(start_pos)
|
||||
var affected_rect = Rect2i(start_pos, Vector2.ZERO)
|
||||
var q = [start_pos]
|
||||
while q.size() > 0:
|
||||
var x = q.pop_front()
|
||||
result_into_mask.set_bitv(x, true)
|
||||
for pos in [x + Vector2i.RIGHT, x + Vector2i.DOWN, x + Vector2i.LEFT, x + Vector2i.UP]:
|
||||
if (
|
||||
is_out_of_bounds(pos, image.get_size())
|
||||
|| result_into_mask.get_bitv(pos)
|
||||
|| (selection != null && !selection.get_bitv(pos))
|
||||
):
|
||||
continue
|
||||
|
||||
if tolerance == 1.0 || get_color_distance_squared(start_color, image.get_pixelv(pos)) <= tolerance:
|
||||
affected_rect = affected_rect.expand(pos)
|
||||
result_into_mask.set_bitv(pos, true)
|
||||
q.append(pos)
|
||||
|
||||
return affected_rect
|
||||
|
||||
|
||||
static func global_fill(
|
||||
image : Image,
|
||||
result_into_mask : BitMap,
|
||||
start_pos : Vector2i,
|
||||
tolerance : float = 0.0
|
||||
) -> Rect2i:
|
||||
var start_color = image.get_pixelv(start_pos)
|
||||
var affected_rect = Rect2i(start_pos, Vector2.ZERO)
|
||||
for i in image.get_width():
|
||||
for j in image.get_height():
|
||||
if tolerance == 1.0 || get_color_distance_squared(start_color, image.get_pixel(i, j)) <= tolerance:
|
||||
affected_rect = affected_rect.expand(Vector2i(i, j))
|
||||
result_into_mask.set_bit(i, j, true)
|
||||
|
||||
return affected_rect
|
||||
|
||||
|
||||
static func get_color_distance_squared(a : Color, b : Color) -> float:
|
||||
if a.a + b.a == 0.0: return 0.0
|
||||
return (
|
||||
(a.r - b.r) * (a.r - b.r)
|
||||
+ (a.g - b.g) * (a.g - b.g)
|
||||
+ (a.b - b.b) * (a.b - b.b)
|
||||
+ (a.a - b.a) * (a.a - b.a)
|
||||
) * 0.33333
|
||||
|
||||
|
||||
static func draw_bitmap(on_node : CanvasItem, bitmap : BitMap, color : Color, offset : Vector2 = Vector2(0, 0)):
|
||||
var map_size = bitmap.get_size()
|
||||
var draw_next = false
|
||||
var rect_height = 0
|
||||
var draw_pos : Vector2
|
||||
for i in map_size.x:
|
||||
for j in map_size.y:
|
||||
if bitmap.get_bit(i, j) != draw_next || j == 0:
|
||||
if rect_height >= 1 && draw_next:
|
||||
draw_pos = Vector2(i, j - rect_height)
|
||||
if j == 0:
|
||||
draw_pos = Vector2(i - 1, map_size.y - rect_height)
|
||||
|
||||
on_node.draw_rect(Rect2(
|
||||
draw_pos + offset,
|
||||
Vector2(1, rect_height)
|
||||
), color)
|
||||
|
||||
rect_height = 0
|
||||
|
||||
rect_height += 1
|
||||
draw_next = bitmap.get_bit(i, j)
|
||||
|
||||
on_node.draw_rect(Rect2(
|
||||
offset + Vector2(map_size.x - 1, map_size.y - rect_height),
|
||||
Vector2(1, rect_height)
|
||||
), color)
|
||||
|
||||
|
||||
static func is_out_of_bounds(pos : Vector2i, rect_size : Vector2i) -> bool:
|
||||
return (
|
||||
pos.x < 0 || pos.y < 0
|
||||
|| pos.x >= rect_size.x || pos.y >= rect_size.y
|
||||
)
|
168
addons/sprite_painter/src/floating/color_picker.gd
Normal file
@ -0,0 +1,168 @@
|
||||
@tool
|
||||
extends MarginContainer
|
||||
|
||||
signal color_changed(new_color, is_primary)
|
||||
|
||||
@export var workspace : NodePath
|
||||
@export var picker_shortcut : Shortcut
|
||||
|
||||
@onready var color1_button = $Container/Box/Box/Box/Control/Color1
|
||||
@onready var color2_button = $Container/Box/Box/Box/Control/Color2
|
||||
@onready var palette = $Container/Box/Box/Palette
|
||||
@onready var which_color_picked = $Container/Box/Box/WhichColor
|
||||
@onready var color_picker_container = $Control/Picker
|
||||
@onready var color_picker = $Control/Picker/Margins/Box/ColorPicker
|
||||
@onready var color_picker_tool_button = $Container/Box/Box/Box/Control/Control.get_child(0)
|
||||
|
||||
var color1 := Color.WHITE
|
||||
var color2 := Color.TRANSPARENT
|
||||
|
||||
var color_picker_primary := true
|
||||
var color_picker_picking := false
|
||||
var color_picker_picking_disable_with_click := false
|
||||
var color_picker_pick_from_image := false
|
||||
var screen_image : Image
|
||||
|
||||
|
||||
func _ready():
|
||||
if get_viewport() is SubViewport: return
|
||||
|
||||
hide()
|
||||
size = Vector2.ZERO
|
||||
show()
|
||||
position = Vector2(0, get_parent().size.y - get_minimum_size().y)
|
||||
|
||||
set_picked_primary(true, false)
|
||||
set_color(true, color1)
|
||||
set_color(false, color2)
|
||||
_on_visibility_changed()
|
||||
_yoink_color_picker_tool_button()
|
||||
hide()
|
||||
show()
|
||||
color_picker_container.hide()
|
||||
color_picker_container.size = Vector2.ZERO
|
||||
await get_tree().process_frame
|
||||
color_picker_container.global_position = (
|
||||
global_position
|
||||
+ get_minimum_size()
|
||||
+ Vector2(16, -color_picker_container.size.y)
|
||||
)
|
||||
|
||||
|
||||
func set_color(is_primary, color):
|
||||
if is_primary:
|
||||
color1 = color
|
||||
color1_button.self_modulate = color
|
||||
|
||||
else:
|
||||
color2 = color
|
||||
color2_button.self_modulate = color
|
||||
|
||||
if color_picker_primary == is_primary:
|
||||
color_picker.color = color
|
||||
|
||||
color_changed.emit(color, is_primary)
|
||||
|
||||
|
||||
func set_picked_primary(is_primary, toggle_picker_shown = true):
|
||||
if toggle_picker_shown:
|
||||
color_picker_container.visible = !color_picker_container.visible
|
||||
|
||||
color_picker_primary = is_primary
|
||||
color_picker.color = color1 if is_primary else color2
|
||||
if !color_picker_container.visible:
|
||||
which_color_picked.text = "Color"
|
||||
|
||||
elif is_primary:
|
||||
which_color_picked.text = "Primary"
|
||||
|
||||
else:
|
||||
which_color_picked.text = "Secondary"
|
||||
|
||||
|
||||
func set_color_screen_picking(picking, hold):
|
||||
color_picker_picking_disable_with_click = !hold
|
||||
color_picker_picking = picking
|
||||
get_node(workspace).input_disabled = picking
|
||||
if picking:
|
||||
screen_image = get_viewport().get_texture().get_image()
|
||||
|
||||
|
||||
func _input(event):
|
||||
if picker_shortcut.matches_event(event) && !event.is_echo():
|
||||
# Only pick primary color (for consistent feel if picker not open)
|
||||
set_color_screen_picking(event.is_pressed(), true)
|
||||
return
|
||||
|
||||
if event is InputEventMouseButton && color_picker_picking:
|
||||
if !event.pressed: return
|
||||
if color_picker_picking_disable_with_click:
|
||||
set_color_screen_picking(false, false)
|
||||
|
||||
if color_picker_pick_from_image:
|
||||
var img_view = get_node(workspace).image_view
|
||||
var imagespace_event = img_view.event_vp_to_image(event)
|
||||
var color = get_node(workspace).edited_image.get_pixelv(imagespace_event.position)
|
||||
set_color(event.button_index == MOUSE_BUTTON_LEFT, color)
|
||||
|
||||
else:
|
||||
set_color(event.button_index == MOUSE_BUTTON_LEFT, screen_image.get_pixelv(event.position))
|
||||
|
||||
|
||||
func _yoink_color_picker_tool_button():
|
||||
var child = color_picker.get_child(1, true);
|
||||
if child == null:
|
||||
return
|
||||
|
||||
for x in child.get_children(true):
|
||||
if !x is Button:
|
||||
continue
|
||||
|
||||
x.free()
|
||||
break
|
||||
|
||||
|
||||
func _on_color_picker_color_changed(color):
|
||||
set_color(color_picker_primary, color)
|
||||
|
||||
|
||||
func _on_color_1_pressed():
|
||||
set_picked_primary(true, !color_picker_container.visible || color_picker_primary)
|
||||
|
||||
|
||||
func _on_color_2_pressed():
|
||||
set_picked_primary(false, !color_picker_container.visible || !color_picker_primary)
|
||||
|
||||
|
||||
func _on_swap_pressed():
|
||||
var swap = color1
|
||||
set_color(true, color2)
|
||||
set_color(false, swap)
|
||||
set_picked_primary(color_picker_primary, false)
|
||||
|
||||
|
||||
func _on_open_picker_toggled(button_pressed):
|
||||
color_picker_container.visible = button_pressed
|
||||
set_picked_primary(color_picker_primary)
|
||||
|
||||
|
||||
func _on_palette_toggled(button_pressed):
|
||||
palette.visible = button_pressed
|
||||
|
||||
|
||||
func _on_visibility_changed():
|
||||
set_process_input(is_visible_in_tree())
|
||||
|
||||
|
||||
func _on_picker_header_gui_input(event):
|
||||
if event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
||||
color_picker_container.position += event.relative
|
||||
|
||||
|
||||
func _on_color_pick_pressed():
|
||||
set_color_screen_picking(!color_picker_picking, false)
|
||||
|
||||
|
||||
func _on_pick_from_image_toggled(button_pressed):
|
||||
color_picker_pick_from_image = button_pressed
|
530
addons/sprite_painter/src/floating/color_picker.tscn
Normal file
@ -0,0 +1,530 @@
|
||||
[gd_scene load_steps=19 format=3 uid="uid://be21fmb246ubn"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/color_picker.gd" id="1_ae0lg"]
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/editor_icon_button.gd" id="1_srmpw"]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_l6hbd"]
|
||||
resource_name = "Alt"
|
||||
keycode = 4194328
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_wy4cg"]
|
||||
resource_name = "I"
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_76p2c"]
|
||||
events = [SubResource("InputEventKey_l6hbd"), SubResource("InputEventKey_wy4cg")]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o2ioo"]
|
||||
draw_center = false
|
||||
border_width_left = 4
|
||||
border_width_top = 4
|
||||
border_width_right = 4
|
||||
border_width_bottom = 4
|
||||
corner_radius_top_left = 2
|
||||
corner_radius_top_right = 2
|
||||
corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
shadow_color = Color(0.8, 0.8, 0.8, 1)
|
||||
shadow_size = 4
|
||||
anti_aliasing = false
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_eeewi"]
|
||||
bg_color = Color(1, 1, 1, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.6, 0.6, 0.6, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_70jsv"]
|
||||
bg_color = Color(1, 1, 1, 1)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(1.5, 1.5, 1.5, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gjl5v"]
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.6, 0.6, 0.6, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cicb3"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5mbmh"]
|
||||
bg_color = Color(1, 1, 1, 1)
|
||||
draw_center = false
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.6, 0.6, 0.6, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_qpcds"]
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xa6r5"]
|
||||
content_margin_left = 5.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 3.0
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_fjbou"]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_0j7g6"]
|
||||
keycode = 88
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_8hnrs"]
|
||||
resource_name = "X"
|
||||
events = [SubResource("InputEventKey_0j7g6")]
|
||||
|
||||
[sub_resource type="Image" id="Image_670mm"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_qaodq"]
|
||||
image = SubResource("Image_670mm")
|
||||
|
||||
[node name="ColorSettings" type="MarginContainer"]
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -110.0
|
||||
offset_right = 80.0
|
||||
grow_vertical = 0
|
||||
mouse_filter = 0
|
||||
script = ExtResource("1_ae0lg")
|
||||
picker_shortcut = SubResource("Shortcut_76p2c")
|
||||
|
||||
[node name="Panel2" type="Panel" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Panel3" type="Panel" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Container" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Box" type="HBoxContainer" parent="Container"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Box" type="VBoxContainer" parent="Container/Box"]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="WhichColor" type="Label" parent="Container/Box/Box"]
|
||||
layout_mode = 2
|
||||
text = "Color"
|
||||
|
||||
[node name="Box" type="HBoxContainer" parent="Container/Box/Box"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Control" type="Control" parent="Container/Box/Box/Box"]
|
||||
custom_minimum_size = Vector2(64, 64)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Color2" type="Button" parent="Container/Box/Box/Box/Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -37.0
|
||||
offset_top = -37.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
tooltip_text = "(Right Click)
|
||||
Secondary Color
|
||||
|
||||
Click to open the Color Picker."
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_eeewi")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_70jsv")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_gjl5v")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_cicb3")
|
||||
|
||||
[node name="Frame" type="Button" parent="Container/Box/Box/Box/Control/Color2"]
|
||||
show_behind_parent = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_5mbmh")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_5mbmh")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_5mbmh")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_qpcds")
|
||||
|
||||
[node name="Color1" type="Button" parent="Container/Box/Box/Box/Control"]
|
||||
self_modulate = Color(0.498039, 0.498039, 0.498039, 1)
|
||||
layout_mode = 0
|
||||
offset_right = 37.0
|
||||
offset_bottom = 37.0
|
||||
tooltip_text = "(Left Click)
|
||||
Primary Color
|
||||
|
||||
Click to open the Color Picker."
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_eeewi")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_70jsv")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_gjl5v")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_cicb3")
|
||||
|
||||
[node name="Frame2" type="Button" parent="Container/Box/Box/Box/Control/Color1"]
|
||||
show_behind_parent = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_5mbmh")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_5mbmh")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_5mbmh")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_qpcds")
|
||||
|
||||
[node name="Control2" type="CenterContainer" parent="Container/Box/Box/Box/Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 38.0
|
||||
offset_bottom = -37.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Swap" type="Button" parent="Container/Box/Box/Box/Control/Control2"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "(X)
|
||||
Swap Primary/Secondary Colors
|
||||
|
||||
The Screen Color Picker (Alt) will always change the Primary (Left Mouse Button) color."
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/hover = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/disabled = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_fjbou")
|
||||
shortcut = SubResource("Shortcut_8hnrs")
|
||||
shortcut_in_tooltip = false
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "Loop"
|
||||
|
||||
[node name="Control" type="CenterContainer" parent="Container/Box/Box/Box/Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = 38.0
|
||||
offset_right = -38.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Pick" type="Button" parent="Container/Box/Box/Box/Control/Control"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "(Alt)
|
||||
Color Sampler
|
||||
|
||||
Can pick color from the editor screen, or the image. Change this in the Color Picker accessed by clicking a color here."
|
||||
theme_override_styles/normal = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/hover = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/disabled = SubResource("StyleBoxEmpty_xa6r5")
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_fjbou")
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "ColorPick"
|
||||
|
||||
[node name="Palette" type="VBoxContainer" parent="Container/Box/Box"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Box" type="HBoxContainer" parent="Container/Box/Box/Palette"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Container/Box/Box/Palette/Box"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Palette"
|
||||
|
||||
[node name="FromImage" type="Button" parent="Container/Box/Box/Palette/Box"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Get from image / selection"
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "CanvasLayer"
|
||||
|
||||
[node name="Palette" type="VFlowContainer" parent="Container/Box/Box/Palette"]
|
||||
custom_minimum_size = Vector2(0, 96)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ColorRect" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect2" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect3" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect4" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect5" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect6" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect7" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect8" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect9" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect10" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect11" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect12" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect27" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect21" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect22" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect23" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect24" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect25" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect26" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect13" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect14" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect15" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect16" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect17" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect18" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect19" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ColorRect20" type="ColorRect" parent="Container/Box/Box/Palette/Palette"]
|
||||
custom_minimum_size = Vector2(16, 16)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="Palette2" type="Button" parent="Container/Box/Box"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
icon_alignment = 1
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "Grid"
|
||||
|
||||
[node name="Control" type="Control" parent="."]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Picker" type="MarginContainer" parent="Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 2.0
|
||||
offset_top = -40.0
|
||||
offset_right = 308.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
metadata/_edit_group_ = true
|
||||
|
||||
[node name="Panel4" type="Panel" parent="Control/Picker"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Panel5" type="Panel" parent="Control/Picker"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Margins" type="MarginContainer" parent="Control/Picker"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="Box" type="VBoxContainer" parent="Control/Picker/Margins"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="Control/Picker/Margins/Box"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Button" type="Button" parent="Control/Picker/Margins/Box/Header"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
disabled = true
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
icon_alignment = 1
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "ToolMove"
|
||||
|
||||
[node name="Button2" type="Button" parent="Control/Picker/Margins/Box/Header"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Toggle color picking from image
|
||||
|
||||
If off, the Picker Tool (Alt) will pick the color from the editor's viewport."
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "SubViewport"
|
||||
|
||||
[node name="Button3" type="Button" parent="Control/Picker/Margins/Box/Header"]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_qaodq")
|
||||
script = ExtResource("1_srmpw")
|
||||
icon_name = "Close"
|
||||
|
||||
[node name="ColorPicker" type="ColorPicker" parent="Control/Picker/Margins/Box"]
|
||||
layout_mode = 2
|
||||
color = Color(0.498039, 0.498039, 0.498039, 1)
|
||||
color_mode = 1
|
||||
sampler_visible = false
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]
|
||||
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color2" to="." method="_on_color_2_pressed"]
|
||||
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color2/Frame" to="." method="_on_color_2_pressed"]
|
||||
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color1" to="." method="_on_color_1_pressed"]
|
||||
[connection signal="pressed" from="Container/Box/Box/Box/Control/Color1/Frame2" to="." method="_on_color_2_pressed"]
|
||||
[connection signal="pressed" from="Container/Box/Box/Box/Control/Control2/Swap" to="." method="_on_swap_pressed"]
|
||||
[connection signal="pressed" from="Container/Box/Box/Box/Control/Control/Pick" to="." method="_on_color_pick_pressed"]
|
||||
[connection signal="toggled" from="Container/Box/Box/Palette2" to="." method="_on_palette_toggled"]
|
||||
[connection signal="gui_input" from="Control/Picker/Margins/Box/Header/Button" to="." method="_on_picker_header_gui_input"]
|
||||
[connection signal="toggled" from="Control/Picker/Margins/Box/Header/Button2" to="." method="_on_pick_from_image_toggled"]
|
||||
[connection signal="pressed" from="Control/Picker/Margins/Box/Header/Button3" to="Control/Picker" method="hide"]
|
||||
[connection signal="color_changed" from="Control/Picker/Margins/Box/ColorPicker" to="." method="_on_color_picker_color_changed"]
|
145
addons/sprite_painter/src/floating/tool_switcher.gd
Normal file
@ -0,0 +1,145 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
signal tool_changed(tool_node)
|
||||
|
||||
@export var toolbar : NodePath
|
||||
@export var toolbar_end : NodePath
|
||||
@export var image_view : NodePath
|
||||
|
||||
var by_button := {}
|
||||
var by_shortcut := {}
|
||||
|
||||
var current_tool : Control
|
||||
var current_tool_shortcut_list := []
|
||||
var current_color1 := Color.WHITE
|
||||
var current_color2 := Color.WHITE
|
||||
|
||||
|
||||
func _ready():
|
||||
if get_viewport() is SubViewport: return
|
||||
|
||||
var end_at = get_node(toolbar_end)
|
||||
for x in get_node(toolbar).get_children():
|
||||
if x == end_at: break
|
||||
if !x is BaseButton: continue
|
||||
_setup_tool_button(x)
|
||||
|
||||
|
||||
func _setup_tool_button(button : BaseButton):
|
||||
var tool_node = get_node_or_null(NodePath(button.name))
|
||||
by_button[button] = tool_node
|
||||
button.disabled = false
|
||||
if tool_node == null:
|
||||
button.disabled = true
|
||||
return
|
||||
|
||||
if button.shortcut != null:
|
||||
var buttons_with_sc = by_shortcut.get(button.shortcut, [])
|
||||
buttons_with_sc.append(button)
|
||||
button.pressed.connect(_on_button_shortcut_pressed.bind(
|
||||
button,
|
||||
buttons_with_sc
|
||||
))
|
||||
if buttons_with_sc.size() >= 2:
|
||||
button.set_deferred("shortcut", null)
|
||||
|
||||
by_shortcut[button.shortcut] = buttons_with_sc
|
||||
if current_tool == null:
|
||||
_on_button_shortcut_pressed(button, buttons_with_sc)
|
||||
|
||||
button.tooltip_text = "%s (%s)" % [
|
||||
tool_node.tool_name,
|
||||
(button.shortcut.get_as_text() + " + ")\
|
||||
.repeat(by_shortcut[button.shortcut].size())\
|
||||
.trim_suffix(" + "),
|
||||
]
|
||||
tool_node.hide()
|
||||
button.toggled.connect(_on_tool_button_toggled.bind(tool_node))
|
||||
if current_tool == null:
|
||||
_on_tool_button_toggled(true, tool_node)
|
||||
|
||||
|
||||
func _on_button_shortcut_pressed(button : BaseButton, list : Array):
|
||||
if button.shortcut == null: return
|
||||
|
||||
var pressed_in_list = list.find(button)
|
||||
if current_tool_shortcut_list != list:
|
||||
# Next time the shortcut is pressed, it will select the first from the list
|
||||
if current_tool_shortcut_list.size() > 1:
|
||||
var sc
|
||||
for x in current_tool_shortcut_list:
|
||||
if x.shortcut != null:
|
||||
sc = x.shortcut
|
||||
x.shortcut = null
|
||||
|
||||
current_tool_shortcut_list[0].shortcut = sc
|
||||
|
||||
list[(pressed_in_list + 1) % list.size()].shortcut = button.shortcut
|
||||
if list.size() > 1:
|
||||
button.shortcut = null
|
||||
|
||||
current_tool_shortcut_list = list
|
||||
|
||||
|
||||
func _on_tool_button_toggled(toggled : bool, tool_node : EditingTool):
|
||||
if !toggled: return
|
||||
|
||||
if current_tool != null:
|
||||
current_tool.hide()
|
||||
|
||||
tool_node.show()
|
||||
current_tool = tool_node
|
||||
|
||||
get_node(image_view).self_modulate.a = 1.0 if tool_node.image_hide_mode != 2 else 0.0
|
||||
tool_changed.emit(tool_node)
|
||||
|
||||
|
||||
func handle_image_input(event, image, selection) -> bool:
|
||||
if current_tool == null: return false
|
||||
if event is InputEventMouseMotion:
|
||||
current_tool.selection = selection # Needed because Image Script tool does not normally use clicks
|
||||
# I better just fetch it from the Workspace but also ehhhh incapsulation
|
||||
current_tool.mouse_moved(event)
|
||||
return true
|
||||
|
||||
elif event is InputEventMouseButton:
|
||||
current_tool.selection = selection
|
||||
current_tool.mouse_pressed(
|
||||
event,
|
||||
image,
|
||||
current_color1 if event.button_index == MOUSE_BUTTON_LEFT else current_color2,
|
||||
current_color1 if event.button_index != MOUSE_BUTTON_LEFT else current_color2
|
||||
)
|
||||
if current_tool.image_hide_mode != 2:
|
||||
get_node(image_view).self_modulate.a = 1.0
|
||||
|
||||
if event.pressed && current_tool.image_hide_mode == 1:
|
||||
get_node(image_view).self_modulate.a = 0.0
|
||||
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func get_affected_rect() -> Rect2i:
|
||||
return current_tool.get_affected_rect()
|
||||
|
||||
|
||||
func draw_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if current_tool == null: return
|
||||
current_tool.draw_preview(image_view, mouse_position)
|
||||
|
||||
|
||||
func draw_shader_preview(image_view : CanvasItem, mouse_position : Vector2i):
|
||||
if current_tool == null: return
|
||||
image_view.material = current_tool.preview_shader
|
||||
current_tool.draw_shader_preview(image_view, mouse_position)
|
||||
|
||||
|
||||
func _on_color_settings_color_changed(new_color, is_primary):
|
||||
if is_primary:
|
||||
current_color1 = new_color
|
||||
|
||||
else:
|
||||
current_color2 = new_color
|
@ -0,0 +1,69 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
@export var label : NodePath
|
||||
@export var frame_data_textbox : NodePath
|
||||
|
||||
var plugin_root
|
||||
var editor_dock
|
||||
var anim_list
|
||||
var frame_list
|
||||
|
||||
var edited_object
|
||||
var current_anim
|
||||
var current_frame
|
||||
|
||||
|
||||
func connect_plugin(plugin_root_node):
|
||||
plugin_root = plugin_root_node
|
||||
|
||||
|
||||
func try_edit(object):
|
||||
get_node(label).hide()
|
||||
get_node(frame_data_textbox).hide()
|
||||
if object is AnimatedSprite2D || object is AnimatedSprite3D:
|
||||
try_edit(object.frames)
|
||||
return
|
||||
|
||||
if object is SpriteFrames:
|
||||
edited_object = object
|
||||
call_deferred("update_spriteframes")
|
||||
|
||||
|
||||
func try_connect_bottom_dock(dock : Control):
|
||||
if dock.get_class() == "SpriteFramesEditor":
|
||||
editor_dock = dock
|
||||
anim_list = dock.get_child(0).get_child(1).get_child(0).get_child(1)
|
||||
anim_list\
|
||||
.item_selected.connect(_on_anim_selected)
|
||||
frame_list = dock.get_child(1).get_child(1).get_child(0).get_child(1)
|
||||
frame_list\
|
||||
.item_clicked.connect(_on_frame_selected)
|
||||
|
||||
|
||||
func update_spriteframes():
|
||||
current_anim = anim_list.get_selected().get_text(0)
|
||||
current_frame = frame_list.get_selected_items()[0]
|
||||
get_node(label).show()
|
||||
get_node(frame_data_textbox).show()
|
||||
get_node(frame_data_textbox).text = "%s : frame %s" % [current_anim, current_frame]
|
||||
|
||||
var frame_tex = edited_object.get_frame(current_anim, current_frame)
|
||||
if frame_tex is AtlasTexture:
|
||||
plugin_root.edit_subresource(
|
||||
frame_tex.atlas.resource_path,
|
||||
frame_tex.region.size,
|
||||
frame_tex.region.position,
|
||||
true
|
||||
)
|
||||
|
||||
else:
|
||||
plugin_root.edit_file(frame_tex.resource_path)
|
||||
|
||||
|
||||
func _on_frame_selected(index, at_pos, mouse_button_index):
|
||||
update_spriteframes()
|
||||
|
||||
|
||||
func _on_anim_selected():
|
||||
update_spriteframes()
|
@ -0,0 +1,48 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
var plugin_root
|
||||
var editor_dock
|
||||
var source_list
|
||||
|
||||
var edited_object
|
||||
|
||||
|
||||
func connect_plugin(plugin_root_node):
|
||||
plugin_root = plugin_root_node
|
||||
|
||||
|
||||
func try_edit(object):
|
||||
if object is TileMap:
|
||||
try_edit(object)
|
||||
return
|
||||
|
||||
if object is TileSet:
|
||||
edited_object = object
|
||||
call_deferred("update_tileset")
|
||||
|
||||
|
||||
func try_connect_bottom_dock(dock : Control):
|
||||
if dock.get_class() == "TileSetEditor":
|
||||
editor_dock = dock
|
||||
source_list = dock.get_child(1).get_child(0).get_child(0)
|
||||
source_list\
|
||||
.item_clicked.connect(_on_source_selected)
|
||||
|
||||
|
||||
func update_tileset():
|
||||
var selected_id = source_list.get_selected_items()[0]
|
||||
var sauce = edited_object.get_source(selected_id)
|
||||
if !(sauce is TileSetAtlasSource):
|
||||
return
|
||||
|
||||
plugin_root.edit_subresource(
|
||||
sauce.get_texture().resource_path,
|
||||
sauce.texture_region_size + sauce.separation,
|
||||
sauce.margins,
|
||||
false
|
||||
)
|
||||
|
||||
|
||||
func _on_source_selected(index, at_pos, mouse_button_index):
|
||||
update_tileset()
|
85
addons/sprite_painter/src/floating/type_context_settings.gd
Normal file
@ -0,0 +1,85 @@
|
||||
@tool
|
||||
extends MarginContainer
|
||||
|
||||
@onready var type_handlers = $"TypeHandlers".get_children()
|
||||
@onready var cursor_pos_info = $"Container/Box/CursorPos"
|
||||
|
||||
var editor_plugin : EditorPlugin
|
||||
var plugin_root : Node
|
||||
|
||||
var input_start := Vector2()
|
||||
var input_end := Vector2()
|
||||
var input_pressed := false
|
||||
|
||||
var edited_object : Object
|
||||
|
||||
|
||||
func _ready():
|
||||
hide()
|
||||
readjust_size()
|
||||
show()
|
||||
|
||||
plugin_root = self
|
||||
while !plugin_root is Window:
|
||||
plugin_root = plugin_root.get_parent()
|
||||
if !plugin_root is SpritePainterRoot:
|
||||
continue
|
||||
|
||||
editor_plugin = plugin_root.editor_plugin
|
||||
plugin_root.object_selected.connect(_on_plugin_object_selected)
|
||||
for x in type_handlers:
|
||||
x.connect_plugin(plugin_root)
|
||||
break
|
||||
|
||||
var c = Control.new()
|
||||
editor_plugin.add_control_to_bottom_panel(c, "AAA")
|
||||
|
||||
for x in c.get_parent().get_children():
|
||||
for y in type_handlers:
|
||||
y.try_connect_bottom_dock(x)
|
||||
|
||||
editor_plugin.remove_control_from_bottom_panel(c)
|
||||
_on_plugin_object_selected(plugin_root.edited_object)
|
||||
|
||||
|
||||
func handle_image_input(event):
|
||||
if event is InputEventMouseMotion:
|
||||
if input_pressed:
|
||||
input_end = event.position.floor()
|
||||
|
||||
else:
|
||||
input_start = event.position.floor()
|
||||
|
||||
elif event is InputEventMouseButton:
|
||||
input_pressed = event.pressed
|
||||
input_end = event.position.floor()
|
||||
|
||||
if input_pressed:
|
||||
cursor_pos_info.text = "Cursor: %s -> %s (size %s)" % [
|
||||
input_start,
|
||||
input_end,
|
||||
(input_end - input_start).abs() + Vector2.ONE
|
||||
]
|
||||
|
||||
else:
|
||||
cursor_pos_info.text = "Cursor: %s" % input_start
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func _on_plugin_object_selected(obj):
|
||||
if obj == null: return
|
||||
for x in type_handlers:
|
||||
x.try_edit(obj)
|
||||
|
||||
edited_object = obj
|
||||
readjust_size()
|
||||
|
||||
|
||||
func readjust_size():
|
||||
size = Vector2.ZERO
|
||||
position = Vector2(get_parent().size) - get_minimum_size() #get_parent().size can be a Vector2i
|
||||
|
||||
|
||||
func _on_visibility_changed():
|
||||
pass
|
172
addons/sprite_painter/src/floating/type_context_settings.tscn
Normal file
@ -0,0 +1,172 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://dcc6kewvuomhc"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/type_context_settings.gd" id="1_7q6dr"]
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/editor_icon_button.gd" id="2_l84pr"]
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/type_context/context_spriteframes.gd" id="3_jtsht"]
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/src/floating/type_context/context_tileset.gd" id="4_mhrcf"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o2ioo"]
|
||||
draw_center = false
|
||||
border_width_left = 4
|
||||
border_width_top = 4
|
||||
border_width_right = 4
|
||||
border_width_bottom = 4
|
||||
corner_radius_top_left = 2
|
||||
corner_radius_top_right = 2
|
||||
corner_radius_bottom_right = 2
|
||||
corner_radius_bottom_left = 2
|
||||
shadow_color = Color(0.8, 0.8, 0.8, 1)
|
||||
shadow_size = 4
|
||||
anti_aliasing = false
|
||||
|
||||
[node name="ContextSettings" type="MarginContainer"]
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -647.0
|
||||
offset_top = -363.0
|
||||
offset_right = -331.0
|
||||
offset_bottom = -312.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
mouse_filter = 0
|
||||
script = ExtResource("1_7q6dr")
|
||||
|
||||
[node name="Panel2" type="Panel" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Panel3" type="Panel" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Container" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Box" type="VBoxContainer" parent="Container"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Grid" type="GridContainer" parent="Container/Box"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="Anim" type="Label" parent="Container/Box/Grid"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Animation"
|
||||
|
||||
[node name="FrameData" type="LineEdit" parent="Container/Box/Grid"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Idle, Frame 8"
|
||||
editable = false
|
||||
expand_to_text_length = true
|
||||
|
||||
[node name="Label2" type="Label" parent="Container/Box/Grid"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "Helpers"
|
||||
|
||||
[node name="Box" type="HBoxContainer" parent="Container/Box/Grid"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ThemeIconButton" type="Button" parent="Container/Box/Grid/Box"]
|
||||
layout_mode = 2
|
||||
script = ExtResource("2_l84pr")
|
||||
icon_name = "GuiVisibilityVisible"
|
||||
|
||||
[node name="Button" type="Button" parent="Container/Box/Grid/Box"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Edit"
|
||||
|
||||
[node name="CursorPos" type="LineEdit" parent="Container/Box"]
|
||||
custom_minimum_size = Vector2(300, 0)
|
||||
layout_mode = 2
|
||||
text = "Cursor: (0, 0) -> (0, 0) (size (0, 0))"
|
||||
editable = false
|
||||
expand_to_text_length = true
|
||||
|
||||
[node name="Control" type="Control" parent="."]
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Extra" type="MarginContainer" parent="Control"]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -704.0
|
||||
offset_top = -143.0
|
||||
offset_right = -398.0
|
||||
offset_bottom = -103.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
metadata/_edit_group_ = true
|
||||
|
||||
[node name="Panel4" type="Panel" parent="Control/Extra"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_o2ioo")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Panel5" type="Panel" parent="Control/Extra"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Margins" type="MarginContainer" parent="Control/Extra"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="Box" type="VBoxContainer" parent="Control/Extra/Margins"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="Control/Extra/Margins/Box"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Button" type="Button" parent="Control/Extra/Margins/Box/Header"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
disabled = true
|
||||
icon_alignment = 1
|
||||
script = ExtResource("2_l84pr")
|
||||
icon_name = "ToolMove"
|
||||
|
||||
[node name="Button2" type="Button" parent="Control/Extra/Margins/Box/Header"]
|
||||
layout_mode = 2
|
||||
script = ExtResource("2_l84pr")
|
||||
icon_name = "Close"
|
||||
|
||||
[node name="TypeHandlers" type="Node" parent="."]
|
||||
|
||||
[node name="Spriteframes" type="Node" parent="TypeHandlers"]
|
||||
script = ExtResource("3_jtsht")
|
||||
label = NodePath("../../Container/Box/Grid/Anim")
|
||||
frame_data_textbox = NodePath("../../Container/Box/Grid/FrameData")
|
||||
|
||||
[node name="Tileset" type="Node" parent="TypeHandlers"]
|
||||
script = ExtResource("4_mhrcf")
|
||||
|
||||
[connection signal="gui_input" from="Control/Extra/Margins/Box/Header/Button" to="." method="_on_picker_header_gui_input"]
|
||||
[connection signal="pressed" from="Control/Extra/Margins/Box/Header/Button2" to="Control/Extra" method="hide"]
|
54
addons/sprite_painter/src/image_script.gd
Normal file
@ -0,0 +1,54 @@
|
||||
class_name ImageScript
|
||||
extends RefCounted
|
||||
|
||||
## Base class for scripts that process images through Sprite painter.
|
||||
##
|
||||
## Extend this script and add the new file to `res://addons/sprite_painter/image_scripts/`
|
||||
## to make it usable in Sprite Painter.
|
||||
|
||||
enum {
|
||||
SCRIPT_PARAM_BOOL, ## Checkbox property. No hints.
|
||||
SCRIPT_PARAM_INT, ## Integer number property. Hints: [MINVALUE, MAXVALUE]
|
||||
SCRIPT_PARAM_FLOAT, ## Floating-point number property. Hints: [MINVALUE, MAXVALUE]
|
||||
SCRIPT_PARAM_ENUM, ## Enumeration property. Hint is an array of names for the created OptionButton.
|
||||
SCRIPT_PARAM_ICON_ENUM, ## Enumeration property. Hint is an dictionary of {ICON : TOOLTIP} pairs. ICON can be a theme icon name or a loaded Texture.
|
||||
SCRIPT_PARAM_ICON_FLAGS, ## Array of Bools property. Hint is an dictionary of {ICON : TOOLTIP} pairs. ICON can be a theme icon or a loaded Texture.
|
||||
SCRIPT_PARAM_RESOURCE, ## Resource property. Hint is the base type of accepted Resources.
|
||||
SCRIPT_PARAM_FILE, ## Resource property. Allows choosing a file from a folder set in a hint.
|
||||
SCRIPT_PARAM_COLOR, ## Color property. No hints.
|
||||
}
|
||||
|
||||
var _params = {}
|
||||
|
||||
## Returns a parameter set through the GUI.
|
||||
##
|
||||
## Key must be same as passed in `_get_param_list()`.
|
||||
func get_param(key : String) -> Variant:
|
||||
return _params[key]
|
||||
|
||||
## Called when the script is loaded: when switching scripts, opening an image, or resetting parameters.
|
||||
func _ready(image : Image):
|
||||
pass
|
||||
|
||||
## Called to preview or apply the script. Must return the result, which can be the same image object.
|
||||
func _get_image(new_image : Image, selection : BitMap) -> Image:
|
||||
return new_image
|
||||
|
||||
## Must return a list of parameters. Each parameter contains:
|
||||
##
|
||||
## - Name
|
||||
##
|
||||
## - Type
|
||||
##
|
||||
## - Default
|
||||
##
|
||||
## - Type Hint, for which refer to ImageScript's class.
|
||||
func _get_param_list():
|
||||
return [
|
||||
[
|
||||
"_get_param_list() Not overriden!",
|
||||
SCRIPT_PARAM_ENUM,
|
||||
0,
|
||||
["Refer to the ImageScript class for more info."]
|
||||
],
|
||||
]
|
202
addons/sprite_painter/src/image_transform_menu.gd
Normal file
@ -0,0 +1,202 @@
|
||||
@tool
|
||||
extends MenuButton
|
||||
|
||||
enum {
|
||||
OPTION_ROTATE_CW,
|
||||
OPTION_ROTATE_CCW,
|
||||
OPTION_FLIP_H,
|
||||
OPTION_FLIP_V,
|
||||
OPTION_CROP,
|
||||
OPTION_BORDER,
|
||||
OPTION_RESIZE,
|
||||
}
|
||||
|
||||
@export var workspace : NodePath
|
||||
|
||||
@onready var width_edit = $"Dialog/Grid/Width"
|
||||
@onready var height_edit = $"Dialog/Grid/Height"
|
||||
|
||||
var original_size := Vector2i()
|
||||
var expand_dir := Vector2i()
|
||||
var percent_view := false
|
||||
var stretch := false
|
||||
var interp := 0
|
||||
|
||||
|
||||
func _ready():
|
||||
var dir_buttons = $"Dialog/Grid/Box/Grid".get_children()
|
||||
$"Dialog/Grid/Box/Stretch".pressed.connect(_on_stretch_pressed)
|
||||
for i in 9:
|
||||
dir_buttons[i].pressed.connect(func():
|
||||
_on_anchor_dir_pressed(Vector2i(1 - i % 3, 1 - i / 3))
|
||||
)
|
||||
|
||||
var p = get_popup()
|
||||
p.id_pressed.connect(_on_id_pressed)
|
||||
p.hide_on_item_selection = false
|
||||
|
||||
if !get_viewport() is Window: return
|
||||
p.set_item_icon(OPTION_ROTATE_CW, get_theme_icon("RotateRight", "EditorIcons"))
|
||||
p.set_item_icon(OPTION_ROTATE_CCW, get_theme_icon("RotateLeft", "EditorIcons"))
|
||||
p.set_item_icon(OPTION_FLIP_H, get_theme_icon("MoveRight", "EditorIcons"))
|
||||
p.set_item_icon(OPTION_FLIP_V, get_theme_icon("MoveDown", "EditorIcons"))
|
||||
# One of them is not real.
|
||||
# p.set_item_icon(OPTION_FLIP_H, get_theme_icon("Hsize", "EditorIcons"))
|
||||
# p.set_item_icon(OPTION_FLIP_V, get_theme_icon("VSize", "EditorIcons"))
|
||||
p.set_item_icon(OPTION_CROP + 1, get_theme_icon("MeshTexture", "EditorIcons"))
|
||||
p.set_item_icon(OPTION_BORDER + 1, get_theme_icon("ToolMove", "EditorIcons"))
|
||||
p.set_item_icon(OPTION_RESIZE + 1, get_theme_icon("DistractionFree", "EditorIcons"))
|
||||
|
||||
|
||||
func _on_id_pressed(id):
|
||||
var old_image = get_node(workspace).edited_image
|
||||
original_size = old_image.get_size()
|
||||
if id == OPTION_CROP:
|
||||
crop(old_image, get_node(workspace).edited_image_selection)
|
||||
return
|
||||
|
||||
elif id == OPTION_RESIZE:
|
||||
$"Dialog".popup_centered()
|
||||
update_entered_size(original_size)
|
||||
return
|
||||
|
||||
elif id == OPTION_BORDER:
|
||||
var new_image = get_node(workspace).get_resized(
|
||||
old_image,
|
||||
original_size + Vector2i(2, 2),
|
||||
Vector2i(0, 0),
|
||||
-1
|
||||
)
|
||||
|
||||
submit_changed_image(new_image)
|
||||
return
|
||||
|
||||
var new_image = Image.create(original_size.x, original_size.y, false, old_image.get_format())
|
||||
new_image.blit_rect(old_image, Rect2(Vector2i.ZERO, original_size), Vector2i.ZERO)
|
||||
match id:
|
||||
OPTION_ROTATE_CW:
|
||||
new_image.rotate_90(CLOCKWISE)
|
||||
|
||||
OPTION_ROTATE_CCW:
|
||||
new_image.rotate_90(COUNTERCLOCKWISE)
|
||||
|
||||
OPTION_FLIP_H:
|
||||
new_image.flip_x()
|
||||
|
||||
OPTION_FLIP_V:
|
||||
new_image.flip_y()
|
||||
|
||||
submit_changed_image(new_image)
|
||||
|
||||
|
||||
func crop(old_image, selection):
|
||||
var result_rect = Rect2i(0, 0, 0, 0)
|
||||
var found_start = false
|
||||
original_size = old_image.get_size()
|
||||
var sel_bits = selection.get_true_bit_count()
|
||||
var crop_selection = sel_bits > 2 && sel_bits < original_size.x * original_size.y
|
||||
for i in original_size.x:
|
||||
for j in original_size.y:
|
||||
if crop_selection:
|
||||
if !selection.get_bit(i, j):
|
||||
continue
|
||||
|
||||
elif old_image.get_pixel(i, j).a < 0.02:
|
||||
continue
|
||||
|
||||
if !found_start:
|
||||
found_start = true
|
||||
result_rect.position = Vector2i(i, j)
|
||||
|
||||
else:
|
||||
result_rect = result_rect.expand(Vector2i(i, j))
|
||||
|
||||
result_rect.size += Vector2i.ONE
|
||||
if result_rect.size == Vector2i.ONE:
|
||||
return
|
||||
|
||||
var new_image = Image.create(
|
||||
result_rect.size.x,
|
||||
result_rect.size.y,
|
||||
false,
|
||||
old_image.get_format()
|
||||
)
|
||||
new_image.blit_rect(old_image, result_rect, Vector2i.ZERO)
|
||||
submit_changed_image(new_image)
|
||||
|
||||
|
||||
func get_entered_size():
|
||||
if percent_view:
|
||||
return Vector2i(
|
||||
original_size.x * width_edit.value * 0.01,
|
||||
original_size.y * height_edit.value * 0.01
|
||||
)
|
||||
|
||||
else:
|
||||
return Vector2i(
|
||||
width_edit.value,
|
||||
height_edit.value
|
||||
)
|
||||
|
||||
|
||||
func update_entered_size(new_size):
|
||||
if percent_view:
|
||||
width_edit.prefix = ""
|
||||
width_edit.value = new_size.x * 100.0 / original_size.x
|
||||
width_edit.suffix = "%"
|
||||
width_edit.step = 0.01
|
||||
|
||||
height_edit.prefix = ""
|
||||
height_edit.value = new_size.y * 100.0 / original_size.y
|
||||
height_edit.suffix = "%"
|
||||
height_edit.step = 0.01
|
||||
|
||||
else:
|
||||
width_edit.prefix = str(original_size.x) + " -> "
|
||||
width_edit.value = new_size.x
|
||||
width_edit.suffix = "px"
|
||||
width_edit.step = 1
|
||||
|
||||
height_edit.prefix = str(original_size.y) + " -> "
|
||||
height_edit.value = new_size.y
|
||||
height_edit.suffix = "px"
|
||||
height_edit.step = 1
|
||||
|
||||
|
||||
func submit_changed_image(new_image):
|
||||
var ws = get_node(workspace)
|
||||
ws.image_replaced.emit(ws.edited_image, new_image)
|
||||
ws.update_texture(new_image)
|
||||
|
||||
|
||||
func _on_dialog_confirmed():
|
||||
var ws = get_node(workspace)
|
||||
var new_image = ws.get_resized(
|
||||
ws.edited_image,
|
||||
get_entered_size(),
|
||||
expand_dir,
|
||||
interp if stretch else -1
|
||||
)
|
||||
|
||||
submit_changed_image(new_image)
|
||||
|
||||
|
||||
func _on_anchor_dir_pressed(direction):
|
||||
$"Dialog/Grid/Interpolation".disabled = true
|
||||
stretch = false
|
||||
expand_dir = direction
|
||||
|
||||
|
||||
func _on_stretch_pressed():
|
||||
$"Dialog/Grid/Interpolation".disabled = false
|
||||
stretch = true
|
||||
|
||||
|
||||
func _on_interpolation_item_selected(index):
|
||||
interp = index
|
||||
|
||||
|
||||
func _on_percent_toggled(v):
|
||||
var old_entered_size = get_entered_size()
|
||||
percent_view = v
|
||||
update_entered_size(old_entered_size)
|
72
addons/sprite_painter/src/image_view/image_grid_view.gd
Normal file
@ -0,0 +1,72 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
|
||||
@export var grid_color := Color.WHITE
|
||||
@export var grid_line_width := 1.0
|
||||
@export var region_color := Color.WHITE
|
||||
@export var region_line_width := 1.0
|
||||
@export var region_outer_color := Color.BLACK
|
||||
|
||||
var image_size := Vector2i()
|
||||
var grid_size := Vector2i()
|
||||
var grid_offset := Vector2i()
|
||||
var is_region := false
|
||||
|
||||
|
||||
func _draw():
|
||||
if grid_size == Vector2i.ZERO: return
|
||||
if is_region:
|
||||
draw_rect(Rect2(
|
||||
Vector2.ZERO,
|
||||
Vector2(image_size.x, grid_offset.y)
|
||||
), region_outer_color)
|
||||
draw_rect(Rect2(
|
||||
Vector2(0, grid_offset.y),
|
||||
Vector2(grid_offset.x, grid_size.y)
|
||||
), region_outer_color)
|
||||
draw_rect(Rect2(
|
||||
Vector2(grid_offset.x + grid_size.x, grid_offset.y),
|
||||
Vector2(image_size.x - grid_offset.x - grid_size.x, grid_size.y)
|
||||
), region_outer_color)
|
||||
draw_rect(Rect2(
|
||||
Vector2(0, grid_offset.y + grid_size.y),
|
||||
Vector2(image_size.x, image_size.y - grid_offset.y - grid_size.y)
|
||||
), region_outer_color)
|
||||
|
||||
draw_rect(Rect2(
|
||||
grid_offset + Vector2i(-region_line_width, -region_line_width),
|
||||
Vector2(grid_size.x + region_line_width * 2, region_line_width)
|
||||
), region_color)
|
||||
draw_rect(Rect2(
|
||||
grid_offset + Vector2i(-region_line_width, 0),
|
||||
Vector2(region_line_width, grid_size.y)
|
||||
), region_color)
|
||||
draw_rect(Rect2(
|
||||
grid_offset + Vector2i(grid_size.x, 0),
|
||||
Vector2(region_line_width, grid_size.y)
|
||||
), region_color)
|
||||
draw_rect(Rect2(
|
||||
grid_offset + Vector2i(-region_line_width, grid_size.y),
|
||||
Vector2(grid_size.x + region_line_width * 2, region_line_width)
|
||||
), region_color)
|
||||
|
||||
return
|
||||
|
||||
var lines = []
|
||||
var line_count = Vector2i(
|
||||
float(image_size.x - grid_offset.x % grid_size.x) / grid_size.x,
|
||||
float(image_size.y - grid_offset.y % grid_size.y) / grid_size.y
|
||||
)
|
||||
lines.resize(line_count.x * 2)
|
||||
for i in line_count.x:
|
||||
lines[i * 2] = Vector2((i + 1) * grid_size.x + grid_offset.x, 0)
|
||||
lines[i * 2 + 1] = lines[i * 2] + Vector2(0, image_size.y)
|
||||
|
||||
draw_multiline(lines, grid_color, grid_line_width)
|
||||
|
||||
lines.resize(line_count.y * 2)
|
||||
for i in line_count.y:
|
||||
lines[i * 2] = Vector2(0, (i + 1) * grid_size.y + grid_offset.y)
|
||||
lines[i * 2 + 1] = lines[i * 2] + Vector2(image_size.x, 0)
|
||||
|
||||
draw_multiline(lines, grid_color, grid_line_width)
|
39
addons/sprite_painter/src/image_view/image_selection_view.gd
Normal file
@ -0,0 +1,39 @@
|
||||
@tool
|
||||
extends Node2D
|
||||
|
||||
@export var selection_color := Color(0.5, 0.5, 0.5, 0.5)
|
||||
|
||||
var selection : BitMap
|
||||
|
||||
|
||||
func _ready():
|
||||
get_node("../../..").image_changed.connect(_on_image_changed)
|
||||
_on_visibility_changed()
|
||||
|
||||
|
||||
func _process(delta):
|
||||
if Engine.get_process_frames() % 10 != 0: return
|
||||
self_modulate.a = abs(1.0 - fmod(Time.get_ticks_msec() * 0.0005, 2.0))
|
||||
|
||||
|
||||
func _draw():
|
||||
if selection == null: return
|
||||
var sel_size = selection.get_size()
|
||||
if selection.get_true_bit_count() == sel_size.x * sel_size.y:
|
||||
return
|
||||
|
||||
ImageFillTools.draw_bitmap(self, selection, selection_color)
|
||||
|
||||
|
||||
func _on_visibility_changed():
|
||||
if !get_viewport() is Window:
|
||||
set_process(false)
|
||||
self_modulate.a = 0.75
|
||||
return
|
||||
|
||||
set_process(is_visible_in_tree())
|
||||
|
||||
|
||||
func _on_image_changed(image, rect_changed):
|
||||
if selection != null:
|
||||
queue_redraw()
|
15
addons/sprite_painter/src/image_view/image_tool_preview.gd
Normal file
@ -0,0 +1,15 @@
|
||||
@tool
|
||||
extends Sprite2D
|
||||
|
||||
@export var draw_handler : NodePath
|
||||
@export var draw_method := "draw_preview"
|
||||
|
||||
var mouse_pos : Vector2
|
||||
|
||||
|
||||
func _draw():
|
||||
get_node(draw_handler).call(draw_method, self, mouse_pos)
|
||||
|
||||
|
||||
func _on_tool_switcher_tool_changed(tool_node):
|
||||
texture = null
|
92
addons/sprite_painter/src/image_view/image_view.gd
Normal file
@ -0,0 +1,92 @@
|
||||
@tool
|
||||
extends Sprite2D
|
||||
|
||||
@export var draw_handler : NodePath
|
||||
|
||||
@onready var border_rect = $"../ImageBorder"
|
||||
@onready var resize_result = $"../ResizeResult"
|
||||
@onready var preview = $"ToolView"
|
||||
@onready var preview_shader = $"SubViewport/ShaderView"
|
||||
@onready var resize_rect = border_rect.get_node("Resize")
|
||||
|
||||
var camera_pos := Vector2.ZERO
|
||||
var mouse_pos := Vector2.ZERO
|
||||
|
||||
|
||||
func zoom(by : Vector2):
|
||||
scale *= by
|
||||
camera_pos *= by
|
||||
update_position()
|
||||
|
||||
|
||||
func translate(by : Vector2):
|
||||
camera_pos += by
|
||||
update_position()
|
||||
|
||||
|
||||
func update_position():
|
||||
update_position_local()
|
||||
resize_rect.rect_scale = global_scale
|
||||
$"SubViewport".size = texture.get_size()
|
||||
|
||||
|
||||
func reset_position():
|
||||
camera_pos = Vector2.ZERO
|
||||
scale = Vector2.ONE
|
||||
update_position()
|
||||
|
||||
|
||||
func update_position_local():
|
||||
position = get_parent().size * 0.5 + camera_pos - texture.get_size() * 0.5
|
||||
centered = false
|
||||
update_texture_view_rect()
|
||||
|
||||
|
||||
func update_position_overlay(edited_node):
|
||||
position = edited_node.global_position - edited_node.get_viewport().get_visible_rect().position
|
||||
centered = false
|
||||
scale = edited_node.global_scale if edited_node is Node2D else edited_node.scale
|
||||
# TODO: fetch source's actual on-screen position.
|
||||
update_texture_view_rect()
|
||||
|
||||
|
||||
func update_texture_view_rect():
|
||||
border_rect.size = scale * (texture.get_size() if texture != null else Vector2.ZERO)
|
||||
border_rect.position = position
|
||||
if centered:
|
||||
border_rect.position -= border_rect.size * 0.5
|
||||
|
||||
|
||||
func _on_resize_preview_changed(current_delta, expand_direction):
|
||||
resize_result.hide()
|
||||
|
||||
var old_size = texture.get_size()
|
||||
var delta_one_axis = round(current_delta.x if expand_direction.x != 0 else current_delta.y)
|
||||
var old_size_one_axis = old_size.x if expand_direction.x != 0 else old_size.y
|
||||
resize_result.text = "%s%spx (%.1f%s)\n-> %s\n" % [
|
||||
"+" if delta_one_axis >= 0.5 else "",
|
||||
delta_one_axis,
|
||||
(1.0 + delta_one_axis / float(old_size_one_axis)) * 100,
|
||||
"%",
|
||||
(old_size + current_delta).round(),
|
||||
]
|
||||
|
||||
resize_result.visible = expand_direction != Vector2i(0, 0)
|
||||
resize_result.global_position = get_global_mouse_position() - resize_result.size * 0.5
|
||||
|
||||
|
||||
func event_vp_to_image(from_event : InputEventMouse, unsafe : bool = false) -> InputEventMouse:
|
||||
if !unsafe:
|
||||
from_event = from_event.duplicate()
|
||||
|
||||
from_event.position = (from_event.position - global_position) / scale
|
||||
if from_event is InputEventMouseMotion:
|
||||
from_event.relative /= scale
|
||||
|
||||
preview.mouse_pos = from_event.position
|
||||
preview.show()
|
||||
preview.queue_redraw()
|
||||
preview_shader.mouse_pos = from_event.position
|
||||
preview_shader.show()
|
||||
preview_shader.queue_redraw()
|
||||
return from_event
|
51
addons/sprite_painter/src/image_view/resize_rect.gd
Normal file
@ -0,0 +1,51 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
signal preview_changed(current_delta, expand_direction)
|
||||
signal value_changed(delta, expand_direction)
|
||||
|
||||
@export var delta_color := Color(1.0, 0.25, 0.0, 0.75)
|
||||
@export var sensitivity := 1.0
|
||||
|
||||
var dragging := false
|
||||
var size_delta := Vector2()
|
||||
var rect_scale := Vector2.ONE
|
||||
var resize_direction := Vector2()
|
||||
|
||||
|
||||
func _ready():
|
||||
$"X-".gui_input.connect(_on_child_gui_input.bind(Vector2.LEFT))
|
||||
$"X+".gui_input.connect(_on_child_gui_input.bind(Vector2.RIGHT))
|
||||
$"Y-".gui_input.connect(_on_child_gui_input.bind(Vector2.UP))
|
||||
$"Y+".gui_input.connect(_on_child_gui_input.bind(Vector2.DOWN))
|
||||
|
||||
|
||||
func _draw():
|
||||
var anchor = (resize_direction + Vector2.ONE) * 0.5
|
||||
|
||||
draw_rect(Rect2(size * anchor.floor(), (
|
||||
Vector2(resize_direction.x * round(size_delta.x) * rect_scale.x, size.y)
|
||||
if size_delta.x != 0.0 else
|
||||
Vector2(size.x, resize_direction.y * round(size_delta.y) * rect_scale.y)
|
||||
)), delta_color)
|
||||
|
||||
|
||||
func _on_child_gui_input(event, direction):
|
||||
if event is InputEventMouseMotion:
|
||||
if !dragging: return
|
||||
size_delta += event.relative * direction * sensitivity / rect_scale
|
||||
preview_changed.emit(size_delta, Vector2i(direction))
|
||||
|
||||
if event is InputEventMouseButton && event.button_index == MOUSE_BUTTON_LEFT:
|
||||
dragging = event.pressed
|
||||
resize_direction = direction
|
||||
if !event.pressed:
|
||||
value_changed.emit(size_delta, Vector2i(direction))
|
||||
preview_changed.emit(Vector2.ZERO, Vector2i.ZERO)
|
||||
size_delta = Vector2.ZERO
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
|
||||
else:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||
|
||||
queue_redraw()
|
89
addons/sprite_painter/src/image_view/resize_rect.tscn
Normal file
@ -0,0 +1,89 @@
|
||||
[gd_scene load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/sprite_painter/src/image_view/resize_rect.gd" id="1_eudu3"]
|
||||
[ext_resource type="Texture2D" uid="uid://c7nymhfcdr3hl" path="res://addons/sprite_painter/graphics/diags.png" id="1_v44m5"]
|
||||
|
||||
[node name="Resize" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_eudu3")
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="X-" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 4
|
||||
anchor_top = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -48.0
|
||||
offset_top = -16.0
|
||||
offset_right = -16.0
|
||||
offset_bottom = 16.0
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 10
|
||||
texture = ExtResource("1_v44m5")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 1
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="X+" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 6
|
||||
anchor_left = 1.0
|
||||
anchor_top = 0.5
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 0.5
|
||||
offset_left = 16.0
|
||||
offset_top = -16.0
|
||||
offset_right = 48.0
|
||||
offset_bottom = 16.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 10
|
||||
texture = ExtResource("1_v44m5")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 1
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Y-" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 5
|
||||
anchor_left = 0.5
|
||||
anchor_right = 0.5
|
||||
offset_left = -16.0
|
||||
offset_top = -48.0
|
||||
offset_right = 16.0
|
||||
offset_bottom = -16.0
|
||||
grow_horizontal = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 9
|
||||
texture = ExtResource("1_v44m5")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 1
|
||||
metadata/_edit_lock_ = true
|
||||
|
||||
[node name="Y+" type="TextureRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -16.0
|
||||
offset_top = 16.0
|
||||
offset_right = 16.0
|
||||
offset_bottom = 48.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 9
|
||||
texture = ExtResource("1_v44m5")
|
||||
ignore_texture_size = true
|
||||
stretch_mode = 1
|
||||
metadata/_edit_lock_ = true
|
204
addons/sprite_painter/src/main.gd
Normal file
@ -0,0 +1,204 @@
|
||||
@tool
|
||||
class_name SpritePainterRoot
|
||||
extends Control
|
||||
|
||||
signal object_selected(object)
|
||||
|
||||
@onready var workspace = $"%Workspace"
|
||||
|
||||
var editor_interface : EditorInterface
|
||||
var editor_plugin : EditorPlugin
|
||||
var editor_2d_vp : Control
|
||||
var editor_3d_vp : Control
|
||||
|
||||
var viewport_ignored := true
|
||||
var edited_object : Object
|
||||
var unsaved_image_paths : Array[String] = []
|
||||
var undo_redo : EditorUndoRedoManager
|
||||
|
||||
|
||||
func _ready():
|
||||
workspace.pre_image_changed.connect(_on_workspace_pre_image_changed)
|
||||
workspace.image_changed.connect(_on_workspace_image_changed)
|
||||
workspace.image_replaced.connect(_on_workspace_image_replaced)
|
||||
|
||||
# var plugin_undoredo = editor_plugin.get_undo_redo()
|
||||
# var own_history_id = plugin_undoredo.get_object_history_id(self)
|
||||
# undo_redo = plugin_undoredo.get_history_undo_redo(own_history_id)
|
||||
# undo_redo = editor_plugin.get_undo_redo()
|
||||
|
||||
|
||||
func edit_object(obj : Object):
|
||||
if obj is Node:
|
||||
edit_node(obj)
|
||||
edited_object = obj
|
||||
object_selected.emit(obj)
|
||||
|
||||
elif obj is CompressedTexture2D:
|
||||
edit_file(obj.resource_path)
|
||||
edited_object = obj
|
||||
object_selected.emit(obj)
|
||||
|
||||
elif obj is AtlasTexture:
|
||||
var region = obj.region
|
||||
edit_subresource(obj.atlas.resource_path, region.size, region.position, true)
|
||||
edited_object = obj
|
||||
object_selected.emit(obj)
|
||||
|
||||
else:
|
||||
object_selected.emit(obj)
|
||||
|
||||
|
||||
func edit_node(node : Node):
|
||||
if node == null:
|
||||
return
|
||||
|
||||
mouse_filter = MOUSE_FILTER_STOP if viewport_ignored else MOUSE_FILTER_IGNORE
|
||||
if "texture" in node:
|
||||
call_deferred("edit_subresource", node.texture.resource_path)
|
||||
|
||||
|
||||
func edit_subresource(
|
||||
filepath : String,
|
||||
grid_size : Vector2i = Vector2i.ZERO,
|
||||
grid_offset : Vector2i = Vector2i.ZERO,
|
||||
is_region : bool = false
|
||||
):
|
||||
if unsaved_image_paths.find(StringName(filepath)) == -1:
|
||||
unsaved_image_paths.append(StringName(filepath))
|
||||
|
||||
if !is_visible_in_tree(): return
|
||||
|
||||
workspace.edit_texture(filepath)
|
||||
if edited_object is Sprite2D || edited_object is Sprite3D:
|
||||
update_grid_from_sprite(edited_object)
|
||||
|
||||
else:
|
||||
workspace.set_view_grid(grid_size, grid_offset, is_region)
|
||||
|
||||
|
||||
func update_grid_from_sprite(node : CanvasItem):
|
||||
var tex_size = Vector2(node.texture.get_size())
|
||||
var frame_size = tex_size / Vector2(node.hframes, node.vframes)
|
||||
var region_offset = Vector2i.ZERO
|
||||
if node.region_enabled:
|
||||
frame_size = node.region_rect.size
|
||||
region_offset = node.region_rect.position
|
||||
|
||||
workspace.set_view_grid(frame_size, region_offset, node.region_enabled)
|
||||
|
||||
|
||||
func edit_file(filepath : String):
|
||||
unsaved_image_paths.append(filepath)
|
||||
workspace.edit_texture(filepath)
|
||||
workspace.set_view_grid(Vector2i.ZERO, Vector2i.ZERO, false)
|
||||
|
||||
|
||||
func _gui_input(event):
|
||||
handle_input(event)
|
||||
|
||||
|
||||
func handle_input(event):
|
||||
return workspace.handle_input(event)
|
||||
|
||||
|
||||
static func print_hierarchy(root : Node, indent : String = ""):
|
||||
for x in root.get_children(true):
|
||||
print(x.name.indent(indent) + " : " + x.get_class())
|
||||
print_hierarchy(x, indent + "- ")
|
||||
|
||||
|
||||
static func save_scene(root : Node, filename = "saved_scn.tscn"):
|
||||
for x in root.get_children(true):
|
||||
pack_to_owner(root)
|
||||
|
||||
var packed = PackedScene.new()
|
||||
packed.pack(root)
|
||||
packed.resource_path = "res://" + filename
|
||||
ResourceSaver.save(packed)
|
||||
|
||||
|
||||
static func pack_to_owner(root : Node, keep_scene_nodes : bool = true, new_owner : Node = root):
|
||||
for x in root.get_children(true):
|
||||
# if keep_scene_nodes && root.file_path != "":
|
||||
# new_owner = root
|
||||
|
||||
x.owner = new_owner
|
||||
pack_to_owner(x, keep_scene_nodes, new_owner)
|
||||
|
||||
|
||||
func _on_close_pressed():
|
||||
workspace.rollback_changes()
|
||||
editor_plugin._on_enable_pressed()
|
||||
|
||||
|
||||
func _on_save_pressed():
|
||||
editor_plugin._on_enable_pressed()
|
||||
|
||||
|
||||
func _on_workspace_pre_image_changed(image : Image, rect):
|
||||
if rect.size.x == 0 || rect.size.y == 0:
|
||||
return
|
||||
|
||||
undo_redo.create_action("Edit image (start: %s, end: %s)" % [rect.position, rect.end])
|
||||
var saved_image = copy_image_rect(image, rect)
|
||||
undo_redo.add_undo_method(self, "paste_image_rect", saved_image, image, rect.position)
|
||||
# undo_redo.add_undo_method(func edit_undo():
|
||||
# paste_image_rect(saved_image, image, rect.position)
|
||||
# )
|
||||
|
||||
|
||||
func _on_workspace_image_changed(image : Image, rect):
|
||||
if rect.size.x == 0 || rect.size.y == 0:
|
||||
return
|
||||
|
||||
var saved_image = copy_image_rect(image, rect)
|
||||
undo_redo.add_do_method(self, "paste_image_rect", saved_image, image, rect.position)
|
||||
# undo_redo.add_do_method(func edit_do():
|
||||
# paste_image_rect(saved_image, image, rect.position)
|
||||
# )
|
||||
undo_redo.commit_action()
|
||||
|
||||
|
||||
func copy_image_rect(from, rect) -> Image:
|
||||
var new_image = Image.create(rect.size.x, rect.size.y, false, from.get_format())
|
||||
new_image.blit_rect(from, rect, Vector2.ZERO)
|
||||
return new_image
|
||||
|
||||
|
||||
func paste_image_rect(from, to, destination = Vector2.ZERO):
|
||||
to.blit_rect(from, Rect2(Vector2.ZERO, from.get_size()), destination)
|
||||
save_changes(to)
|
||||
|
||||
|
||||
func save_changes(image = null):
|
||||
if image == null:
|
||||
image = workspace.edited_image
|
||||
if image == null: return
|
||||
|
||||
var err = image.save_png(workspace.edited_image_path.get_basename() + ".png")
|
||||
if err != OK: printerr(err)
|
||||
workspace.update_texture(image)
|
||||
|
||||
|
||||
func _on_workspace_image_replaced(old_image, new_image):
|
||||
undo_redo.create_action("Resize image (%s -> %s)" % [old_image.get_size(), new_image.get_size()])
|
||||
undo_redo.add_undo_method(self, "save_changes",
|
||||
copy_image_rect(old_image, Rect2i(Vector2i.ZERO, old_image.get_size()))
|
||||
)
|
||||
undo_redo.add_do_method(self, "save_changes",
|
||||
copy_image_rect(new_image, Rect2i(Vector2i.ZERO, new_image.get_size()))
|
||||
)
|
||||
# undo_redo.add_undo_method(func edit_undo():
|
||||
# workspace.edited_image = copy_image_rect(
|
||||
# old_image,
|
||||
# Rect2i(Vector2i.ZERO, old_image.get_size())
|
||||
# )
|
||||
# )
|
||||
# undo_redo.add_do_method(func edit_do():
|
||||
# workspace.edited_image = copy_image_rect(
|
||||
# new_image,
|
||||
# Rect2i(Vector2i.ZERO, new_image.get_size())
|
||||
# )
|
||||
# )
|
||||
undo_redo.commit_action()
|
1011
addons/sprite_painter/src/main.tscn
Normal file
203
addons/sprite_painter/src/workspace.gd
Normal file
@ -0,0 +1,203 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
signal pre_image_changed(image, rect_changed)
|
||||
signal image_changed(image, rect_changed)
|
||||
signal image_replaced(old_image, new_image)
|
||||
|
||||
@onready var image_view = $"%EditedImageView"
|
||||
@onready var selection_view = $"%SelectionView"
|
||||
@onready var grid_view = $"%GridView"
|
||||
@onready var tool_manager = $"%ToolSwitcher"
|
||||
@onready var context_settings = $"%ContextSettings"
|
||||
|
||||
var cur_scale := 1.0
|
||||
var mouse_button := -1
|
||||
var input_disabled := false
|
||||
var dragging := false
|
||||
var rollback_to_image : Image
|
||||
var edited_image : Image
|
||||
var edited_image_path : String
|
||||
var edited_image_selection := BitMap.new()
|
||||
|
||||
|
||||
func _ready():
|
||||
if !get_viewport() is Window: return
|
||||
|
||||
# Recolor all floating panels to make buttons more visible
|
||||
var style = get_theme_stylebox("panel", "Panel").duplicate()
|
||||
style.bg_color = get_theme_color("base_color", "Editor")
|
||||
theme = Theme.new()
|
||||
theme.set_stylebox("panel", "Panel", style)
|
||||
|
||||
|
||||
func handle_input(event) -> bool:
|
||||
if input_disabled: return true
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT || event.button_index == MOUSE_BUTTON_LEFT:
|
||||
if event.pressed:
|
||||
dragging = true
|
||||
mouse_button = event.button_index
|
||||
return pass_event_to_tool(event)
|
||||
|
||||
elif mouse_button == event.button_index:
|
||||
dragging = false
|
||||
mouse_button = -1
|
||||
var rect = tool_manager.get_affected_rect()
|
||||
make_image_edit(func(): pass_event_to_tool(event), rect)
|
||||
return true
|
||||
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
|
||||
image_view.zoom(Vector2.ONE * 1.05)
|
||||
return true
|
||||
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
||||
image_view.zoom(Vector2.ONE / 1.05)
|
||||
return true
|
||||
|
||||
if event.button_index == MOUSE_BUTTON_MIDDLE:
|
||||
return true
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
if Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE) || Input.is_key_pressed(KEY_SPACE):
|
||||
image_view.translate(event.relative)
|
||||
|
||||
grab_focus()
|
||||
return pass_event_to_tool(event)
|
||||
|
||||
if event is InputEventKey:
|
||||
var ctrl = (event.get_keycode_with_modifiers() & (KEY_MASK_CMD_OR_CTRL)) != 0
|
||||
match event.keycode:
|
||||
KEY_A, KEY_D:
|
||||
if ctrl:
|
||||
clear_selection()
|
||||
|
||||
return ctrl
|
||||
|
||||
return false
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func make_image_edit(edit : Callable, affected_rect : Rect2i):
|
||||
pre_image_changed.emit(edited_image, affected_rect)
|
||||
edit.call()
|
||||
image_changed.emit(edited_image, affected_rect)
|
||||
if edited_image_selection.get_true_bit_count() == 0:
|
||||
clear_selection()
|
||||
|
||||
image_view.texture.update(edited_image)
|
||||
|
||||
|
||||
func pass_event_to_tool(event) -> bool:
|
||||
event.position = event.global_position
|
||||
var image_space_event = image_view.event_vp_to_image(event)
|
||||
if context_settings.handle_image_input(image_space_event):
|
||||
return true
|
||||
|
||||
return tool_manager.handle_image_input(
|
||||
image_space_event,
|
||||
edited_image,
|
||||
edited_image_selection
|
||||
)
|
||||
|
||||
|
||||
func edit_texture(tex_path : String):
|
||||
var new_image = Image.load_from_file(tex_path)
|
||||
if new_image.get_format() == Image.FORMAT_RGB8:
|
||||
# If no alpha, add alpha.
|
||||
var image_bytes := new_image.get_data()
|
||||
var new_image_bytes := PackedByteArray()
|
||||
new_image_bytes.resize(image_bytes.size() / 3 * 4)
|
||||
for i in image_bytes.size() / 3:
|
||||
new_image_bytes[i * 4 + 0] = image_bytes[i * 3 + 0]
|
||||
new_image_bytes[i * 4 + 1] = image_bytes[i * 3 + 1]
|
||||
new_image_bytes[i * 4 + 2] = image_bytes[i * 3 + 2]
|
||||
new_image_bytes[i * 4 + 3] = 255
|
||||
|
||||
new_image.set_data(new_image.get_width(), new_image.get_height(), new_image.has_mipmaps(), Image.FORMAT_RGBA8, new_image_bytes)
|
||||
|
||||
rollback_to_image = Image.create_from_data(new_image.get_width(), new_image.get_height(), new_image.has_mipmaps(), new_image.get_format(), new_image.get_data())
|
||||
image_view.texture = ImageTexture.create_from_image(new_image)
|
||||
image_view.reset_position()
|
||||
replace_image(edited_image, new_image)
|
||||
|
||||
image_view.call_deferred("update_position")
|
||||
edited_image_path = tex_path
|
||||
|
||||
|
||||
func rollback_changes():
|
||||
image_view.texture = ImageTexture.create_from_image(rollback_to_image)
|
||||
replace_image(edited_image, rollback_to_image)
|
||||
|
||||
|
||||
func set_view_grid(grid_size, grid_offset, is_region):
|
||||
grid_view.image_size = edited_image.get_size()
|
||||
grid_view.grid_size = grid_size
|
||||
grid_view.grid_offset = grid_offset
|
||||
grid_view.is_region = is_region
|
||||
grid_view.queue_redraw()
|
||||
|
||||
|
||||
func update_texture(new_image):
|
||||
if new_image.get_size() != edited_image.get_size():
|
||||
image_view.texture = ImageTexture.create_from_image(new_image)
|
||||
image_view.update_position()
|
||||
image_view.queue_redraw()
|
||||
replace_image(edited_image, new_image)
|
||||
|
||||
image_view.texture.update(new_image)
|
||||
edited_image = new_image
|
||||
|
||||
grid_view.image_size = new_image.get_size()
|
||||
grid_view.queue_redraw()
|
||||
|
||||
|
||||
func get_resized(old_image, new_size, expand_direction, stretch_format = -1) -> Image:
|
||||
var old_size = old_image.get_size()
|
||||
if stretch_format != -1:
|
||||
var new_image = Image.create(old_size.x, old_size.y, false, old_image.get_format())
|
||||
new_image.blit_rect(
|
||||
old_image,
|
||||
Rect2i(Vector2.ZERO, old_image.get_size()), Vector2i.ZERO
|
||||
)
|
||||
new_image.resize(new_size.x, new_size.y, stretch_format)
|
||||
return new_image
|
||||
|
||||
else:
|
||||
var new_image = Image.create(new_size.x, new_size.y, false, old_image.get_format())
|
||||
var anchors = (Vector2i.ONE - expand_direction) * 0.5
|
||||
new_image.blit_rect(
|
||||
old_image,
|
||||
Rect2i(Vector2i.ZERO, old_image.get_size()),
|
||||
Vector2i(
|
||||
(new_size.x - old_size.x) * anchors.x,
|
||||
(new_size.y - old_size.y) * anchors.y,
|
||||
)
|
||||
)
|
||||
return new_image
|
||||
|
||||
|
||||
func replace_image(old_image, new_image):
|
||||
var new_size = new_image.get_size()
|
||||
edited_image = new_image
|
||||
|
||||
edited_image_selection.create(new_size)
|
||||
clear_selection()
|
||||
selection_view.selection = edited_image_selection
|
||||
|
||||
|
||||
func clear_selection():
|
||||
edited_image_selection.set_bit_rect(Rect2i(Vector2i.ZERO, edited_image_selection.get_size()), true)
|
||||
|
||||
|
||||
func _on_resize_value_changed(
|
||||
delta, expand_direction,
|
||||
stretch_format = Image.INTERPOLATE_NEAREST if Input.is_key_pressed(KEY_SHIFT) else -1
|
||||
):
|
||||
var old_size = edited_image.get_size()
|
||||
var new_size = old_size + Vector2i(delta.round())
|
||||
|
||||
var new_image = get_resized(edited_image, new_size, expand_direction, stretch_format)
|
||||
image_replaced.emit(edited_image, new_image)
|
||||
update_texture(new_image)
|
BIN
amogus.kra
Normal file
BIN
assets/Amogus.png
Normal file
After Width: | Height: | Size: 180 B |
34
assets/Amogus.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://blmo4uqveqgtu"
|
||||
path="res://.godot/imported/Amogus.png-c83f0e9d5398aea3025fb80985b16828.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/Amogus.png"
|
||||
dest_files=["res://.godot/imported/Amogus.png-c83f0e9d5398aea3025fb80985b16828.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
BIN
assets/amogus.kra
Normal file
34
icon.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b7n80wgfsnaml"
|
||||
path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.png"
|
||||
dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
0
images/.gdignore
Normal file
BIN
images/spritepainter1.png
Normal file
After Width: | Height: | Size: 99 KiB |
BIN
images/spritepainter2.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
images/spritepainter3.png
Normal file
After Width: | Height: | Size: 112 KiB |
@ -26,7 +26,20 @@ window/stretch/aspect="keep_width"
|
||||
|
||||
project/assembly_name="TestProject"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/sprite_painter/plugin.cfg")
|
||||
|
||||
[input]
|
||||
|
||||
left_click={
|
||||
"deadzone": 0.5,
|
||||
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":1,"canceled":false,"pressed":false,"double_click":false,"script":null)
|
||||
]
|
||||
}
|
||||
|
||||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
|
BIN
test/diags2.png
Normal file
After Width: | Height: | Size: 719 B |
34
test/diags2.png.import
Normal file
@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cejv38d7lmges"
|
||||
path="res://.godot/imported/diags2.png-3ad0e4c68a2af359ead886b6bff1377e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://test/diags2.png"
|
||||
dest_files=["res://.godot/imported/diags2.png-3ad0e4c68a2af359ead886b6bff1377e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
1
test/icon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg>
|
After Width: | Height: | Size: 4.1 KiB |
37
test/icon.svg.import
Normal file
@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bjmypnfjkbve6"
|
||||
path="res://.godot/imported/icon.svg-9ffd99e4b8a55380f3055552bd81b639.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://test/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-9ffd99e4b8a55380f3055552bd81b639.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
28
test/node_2d.tscn
Normal file
@ -0,0 +1,28 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://dq1nb7p2ts0ok"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://c7nymhfcdr3hl" path="res://addons/sprite_painter/graphics/diags.png" id="1_hgr16"]
|
||||
[ext_resource type="Texture2D" uid="uid://b7n80wgfsnaml" path="res://icon.png" id="1_uj0gm"]
|
||||
[ext_resource type="Texture2D" uid="uid://cejv38d7lmges" path="res://test/diags2.png" id="2_lxinl"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
|
||||
[node name="Polygon2D2" type="Polygon2D" parent="."]
|
||||
texture_repeat = 2
|
||||
position = Vector2(-537, 434)
|
||||
texture = ExtResource("1_hgr16")
|
||||
polygon = PackedVector2Array(302, 105, -2, 293, 224, 391, 330, 537, 478, 327, 567, 292, 640, 199, 842, 581, 852, 669, 1112, 443, 904, 241, 868, 107, 665.661, 13.9704, 520, -53, 418, 52)
|
||||
|
||||
[node name="a" type="Sprite2D" parent="."]
|
||||
position = Vector2(411, 128)
|
||||
texture = ExtResource("2_lxinl")
|
||||
centered = false
|
||||
|
||||
[node name="Icon2" type="Sprite2D" parent="."]
|
||||
position = Vector2(43, 131)
|
||||
texture = ExtResource("1_uj0gm")
|
||||
centered = false
|
||||
|
||||
[node name="Icon4" type="Sprite2D" parent="."]
|
||||
position = Vector2(281, 520)
|
||||
texture = ExtResource("1_uj0gm")
|
||||
centered = false
|
@ -1,9 +1,10 @@
|
||||
[gd_scene load_steps=8 format=4 uid="uid://d4e2mg6r7kt4m"]
|
||||
[gd_scene load_steps=9 format=4 uid="uid://d4e2mg6r7kt4m"]
|
||||
|
||||
[ext_resource type="Script" path="res://world.gd" id="1_336so"]
|
||||
[ext_resource type="Texture2D" uid="uid://hksnxmrpo2cp" path="res://assets/tileset.png" id="1_wepw1"]
|
||||
[ext_resource type="Script" path="res://tile_map.gd" id="2_wnvvd"]
|
||||
[ext_resource type="Texture2D" uid="uid://ws7nut2tt600" path="res://assets/moonbase.png" id="3_hky6b"]
|
||||
[ext_resource type="PackedScene" uid="uid://dtttpb73i7qt2" path="res://Scenes/player.tscn" id="5_2xbni"]
|
||||
|
||||
[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_fyh4j"]
|
||||
texture = ExtResource("1_wepw1")
|
||||
@ -761,6 +762,9 @@ tile_set = SubResource("TileSet_twrk0")
|
||||
[node name="buildings" type="Node2D" parent="TileMap"]
|
||||
z_index = 2
|
||||
|
||||
[node name="Player" parent="TileMap/buildings" instance=ExtResource("5_2xbni")]
|
||||
position = Vector2(603, 507)
|
||||
|
||||
[node name="buildings1" type="TileMapLayer" parent="TileMap/buildings"]
|
||||
z_index = 3
|
||||
z_as_relative = false
|
||||
|