Skip to content

Commit 8d33a1a

Browse files
committed
Add image cropping area which allows panning and zooming in bounds
1 parent b0e57fa commit 8d33a1a

7 files changed

Lines changed: 308 additions & 27 deletions

File tree

MainApplication.tscn

Lines changed: 133 additions & 12 deletions
Large diffs are not rendered by default.

components/PreviewContainer.tscn

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[ext_resource type="Texture2D" uid="uid://dmupwt57kqn2w" path="res://assets/icons/Image.svg" id="2_6wrvd"]
55
[ext_resource type="FontFile" uid="uid://d10xdr4mlux7w" path="res://assets/fonts/cantarell/Cantarell-Regular.ttf" id="3_kats2"]
66

7-
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kx105"]
7+
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7sarn"]
88
resource_local_to_scene = true
99
content_margin_left = 2.0
1010
content_margin_right = 2.0
@@ -13,16 +13,16 @@ border_width_left = 1
1313
border_width_top = 1
1414
border_width_right = 1
1515
border_width_bottom = 1
16-
border_color = Color(0, 0.8, 0.25098, 1)
16+
border_color = Color(0, 0.8, 0.25098, 0)
1717

18-
[sub_resource type="FontVariation" id="FontVariation_lix7p"]
18+
[sub_resource type="FontVariation" id="FontVariation_r4gvt"]
1919
base_font = ExtResource("3_kats2")
2020

2121
[node name="PanelContainer" type="PanelContainer"]
2222
custom_minimum_size = Vector2(0, 80)
2323
offset_right = 80.0
2424
offset_bottom = 80.0
25-
theme_override_styles/panel = SubResource("StyleBoxFlat_kx105")
25+
theme_override_styles/panel = SubResource("StyleBoxFlat_7sarn")
2626
script = ExtResource("1_7r3w4")
2727

2828
[node name="VBoxContainer" type="VBoxContainer" parent="."]
@@ -39,7 +39,7 @@ stretch_mode = 5
3939
[node name="Label" type="Label" parent="VBoxContainer"]
4040
unique_name_in_owner = true
4141
layout_mode = 2
42-
theme_override_fonts/font = SubResource("FontVariation_lix7p")
42+
theme_override_fonts/font = SubResource("FontVariation_r4gvt")
4343
theme_override_font_sizes/font_size = 10
4444
text = "1"
4545
horizontal_alignment = 1

scripts/Application.gd

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,30 @@ signal new_files_loaded
1111
signal load_subdirectories_changed(flag: bool)
1212
signal path_invalid(error: int)
1313

14+
signal active_item_changed(item: ProcessedImage)
15+
16+
signal crop_size_changed(size: Vector2i)
17+
1418
var currently_loaded_files: Array[ProcessedImage] = []
1519

16-
var current_index := 0
20+
var current_loading_index := 0
21+
var currently_active_item: ProcessedImage
22+
23+
var crop_to_size: Vector2i = Vector2i(512,512) :
24+
set(value):
25+
crop_to_size = value
26+
crop_size_changed.emit(value)
27+
28+
var current_active_index := -1 :
29+
set(value):
30+
if current_active_index > -1 and currently_active_item:
31+
currently_active_item.is_active = false
32+
33+
if value > -1 and value < currently_loaded_files.size():
34+
current_active_index = value
35+
currently_active_item = currently_loaded_files[current_active_index]
36+
active_item_changed.emit(currently_active_item)
37+
currently_active_item.is_active = true
1738

1839
var thread_polling_timer: Timer
1940
var thread_workers: Array[ImageThreadWorker] = []
@@ -46,8 +67,6 @@ func _ready() -> void:
4667
thread_polling_timer.timeout.connect(_poll_threads)
4768
thread_polling_timer.paused = false
4869
thread_polling_timer.start()
49-
50-
5170

5271
func _poll_threads() -> void:
5372

@@ -94,16 +113,16 @@ func _entry_path_changed() -> void:
94113
# Now create the ProcessedImages instances
95114
for file_path in file_list:
96115

97-
var image := ProcessedImage.new(current_index, file_path)
116+
var image := ProcessedImage.new(current_loading_index, file_path)
98117
currently_loaded_files.append(image)
99-
current_index += 1
118+
current_loading_index += 1
100119

101120
new_files_loaded.emit()
102121

103122
func clear() -> void:
104123
pre_clear.emit()
105124
currently_loaded_files.clear()
106-
current_index = 0
125+
current_loading_index = 0
107126
post_clear.emit()
108127

109128
class ImageThreadWorkerTask extends Resource:

scripts/ImageScrollManager.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,4 @@ func _process(delta: float) -> void:
128128

129129
if preview_list.size() == Application.currently_loaded_files.size():
130130
filling_images = false
131+
Application.current_active_index = 0

scripts/ImageViewManager.gd

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
extends Sprite2D
2+
3+
var current_image: ProcessedImage
4+
@onready
5+
var cutout_area : Control = %CutOutArea
6+
var minimum_zoom : float
7+
var space_available_from_center : Vector2
8+
var additional_offset : Vector2
9+
var size: Vector2
10+
var old_relative_position: Vector2
11+
var pinch_zoom_start: Vector2
12+
13+
# Called when the node enters the scene tree for the first time.
14+
func _ready() -> void:
15+
Application.active_item_changed.connect(update_image)
16+
cutout_area.resized.connect(update_positions)
17+
18+
func update_image(image: ProcessedImage) -> void:
19+
20+
if current_image != null:
21+
current_image.was_loaded.disconnect(update_image.bind(image))
22+
23+
current_image = image
24+
25+
if image.original_texture == null:
26+
image.load_image()
27+
image.was_loaded.connect(update_image.bind(image))
28+
29+
texture = image.original_texture
30+
31+
func update_positions() -> void:
32+
if texture == null:
33+
return
34+
35+
# Set size to original texture size
36+
size = texture.get_size()
37+
38+
# Adjust the scale so that it fits our aspectratio container
39+
var container_ratio := float(Application.crop_to_size.x)/float(Application.crop_to_size.y)
40+
var image_ratio := float(size.x)/float(size.y)
41+
42+
var scale_tmp : float
43+
44+
if image_ratio < container_ratio:
45+
scale_tmp = cutout_area.size.x / size.x
46+
else:
47+
scale_tmp = cutout_area.size.y / size.y
48+
49+
minimum_zoom = scale_tmp
50+
scale = Vector2(scale_tmp, scale_tmp)
51+
52+
func _input(event: InputEvent) -> void:
53+
54+
55+
# Check for mouse events to update the position
56+
57+
if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
58+
59+
additional_offset -= event.relative
60+
clamp_position()
61+
62+
63+
if event is InputEventMouseMotion and Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
64+
65+
if event.relative.x > 0:
66+
scale *= 1.0 + event.relative.x / 200
67+
68+
69+
elif event.relative.x < 0:
70+
scale *= 1.0 + event.relative.x / 200
71+
72+
73+
if event.relative.y > 0:
74+
scale *= 1.0 + event.relative.y / 1000
75+
76+
77+
elif event.relative.y < 0:
78+
scale *= 1.0 + event.relative.y / 1000
79+
80+
if scale.x < minimum_zoom:
81+
scale = Vector2(minimum_zoom, minimum_zoom)
82+
83+
84+
additional_offset += (pinch_zoom_start - get_local_mouse_position())*scale
85+
86+
87+
88+
if event is InputEventMouseButton:
89+
90+
if event.button_index == MOUSE_BUTTON_RIGHT:
91+
if event.pressed:
92+
pinch_zoom_start = get_local_mouse_position()
93+
94+
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
95+
scale *= 1.05
96+
additional_offset += (old_relative_position - get_local_mouse_position())*scale
97+
98+
99+
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
100+
scale *= 0.9
101+
102+
if scale.x < minimum_zoom:
103+
scale = Vector2(minimum_zoom, minimum_zoom)
104+
additional_offset += (old_relative_position - get_local_mouse_position())*scale
105+
106+
clamp_position()
107+
108+
109+
old_relative_position = get_local_mouse_position()
110+
111+
func clamp_position() -> void:
112+
space_available_from_center = ((size - (cutout_area.size / scale))/2)*scale
113+
114+
if abs(additional_offset.x) > space_available_from_center.x:
115+
additional_offset.x = space_available_from_center.x * sign(additional_offset.x)
116+
if abs(additional_offset.y) > space_available_from_center.y:
117+
additional_offset.y = space_available_from_center.y * sign(additional_offset.y)
118+
119+
position = -additional_offset

scripts/PreviewContainer.gd

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ var label: Label = %Label
1212
@onready
1313
var texture : TextureRect = %Texture
1414

15-
var is_active: bool = false
15+
var stylebox: StyleBoxFlat
1616

1717
# Called when the node enters the scene tree for the first time.
1818
func _ready() -> void:
@@ -21,7 +21,9 @@ func _ready() -> void:
2121
image_reference.was_loaded.connect(set_texture)
2222
image_reference.was_exported.connect(set_texture)
2323
image_reference.was_unloaded.connect(set_texture)
24+
image_reference.active_changed.connect(update_outline)
2425

26+
stylebox = get("theme_override_styles/panel")
2527

2628
# Set the context of the Label to our image_reference index
2729
update_label()
@@ -39,14 +41,26 @@ func update_label() -> void:
3941
label.text = str(image_reference.index) + " - " + image_reference.get_file_name()
4042
else:
4143
label.text = str(image_reference.index)
42-
43-
func set_texture() -> void:
44+
45+
func update_outline() -> void:#
4446

4547
if image_reference == null:
4648
return
49+
50+
if image_reference.is_active:
51+
stylebox.bg_color = Color(0.2, 0.2, 0.2)
52+
else:
53+
stylebox.bg_color = Color(0.1, 0.1, 0.1)
54+
4755

4856
if image_reference.was_already_exported:
49-
pass
57+
stylebox.border_color = Color(0.0247, 0.3438, 0.1081)
58+
59+
60+
func set_texture() -> void:
61+
62+
if image_reference == null:
63+
return
5064

5165
if image_reference.preview_texture != null:
5266
texture.texture = image_reference.preview_texture

scripts/ProcessedImage.gd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ signal was_exported
55
signal was_loaded
66
signal was_unloaded
77

8+
signal active_changed
9+
810
var source_path: String
911
var processed_path: String
1012
var is_currently_loaded := false
@@ -17,6 +19,11 @@ var index: int = 0
1719

1820
var is_loading_image: bool = false
1921

22+
var is_active: bool = false :
23+
set(value):
24+
is_active = value
25+
active_changed.emit()
26+
2027
func _init(index: int, source_path: String) -> void:
2128
self.index = index
2229
self.source_path = source_path

0 commit comments

Comments
 (0)