aboutsummaryrefslogtreecommitdiff
path: root/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd
blob: 20d2ae3b6b67eaf5f593e3698c0d1fb7a082fafc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
tool
extends Control
# Listing of voxels in VoxelSet, with the ability to search, select and edit voxels.



## Signals
# Emitted when voxel has been selected
signal selected_voxel(voxel_id)
# Emitted when voxel has been unselected
signal unselected_voxel(voxel_id)



## Constants
const VoxelButton := preload("res://addons/voxel-core/controls/voxel_button/voxel_button.tscn")



## Exported Variables
# Search being done
export var search := "" setget set_search

# Flag indicating whether edits are allowed
export var allow_edit := false setget set_edit_mode

# Number of uv positions that can be selected at any one time
export(int, -1, 256) var selection_max := 0 setget set_selection_max

# Flag indicating whether Hints is visible
export var show_hints := false setget set_show_hints

# VoxelSet being used
export(Resource) var voxel_set = null setget set_voxel_set



## Public Variables
# UndoRedo used to commit operations
var undo_redo : UndoRedo



## Private Variables
# Selected voxel ids
var _selections := []



## OnReady Variables
onready var Search := get_node("VBoxContainer/Search")

onready var Voxels := get_node("VBoxContainer/ScrollContainer/Voxels")

onready var Hints := get_node("VBoxContainer/Hints")

onready var Hint := get_node("VBoxContainer/Hints/Hint")

onready var ContextMenu := get_node("ContextMenu")



## Built-In Virtual Methods
func _ready():
	set_show_hints(show_hints)
	set_voxel_set(voxel_set)
	
	if not is_instance_valid(undo_redo):
		undo_redo = UndoRedo.new()



## Public Methods
# Sets search, and calls on update_view by default
func set_search(value : String, update := true) -> void:
	search = value
	
	if is_instance_valid(Search):
		Search.text = search
	if update:
		update_view()


# Sets allow_edit
func set_edit_mode(value : bool, update := true) -> void:
	allow_edit = value


# Sets selection_max, and shrinks _selections to new maximum if needed
func set_selection_max(value : int) -> void:
	selection_max = clamp(value, -1, 256)
	unselect_shrink()


# Setter for show_hints
func set_show_hints(value := show_hints) -> void:
	show_hints = value
	
	if is_instance_valid(Hints):
		Hints.visible = show_hints and (allow_edit or selection_max)
		if show_hints:
			Hint.text = ""
			if allow_edit:
				Hint.text += "right click : context menu"
			if selection_max == -1 or selection_max > 1:
				Hint.text += ", ctrl + left click : multiple select / unselect"


# Setter for voxel_set
func set_voxel_set(value : Resource, update := true) -> void:
	if not (typeof(value) == TYPE_NIL or value is VoxelSet):
		printerr("Invalid Resource given expected VoxelSet")
		return
	
	if is_instance_valid(voxel_set):
		if voxel_set.is_connected("requested_refresh", self, "update_view"):
			voxel_set.disconnect("requested_refresh", self, "update_view") 
	
	voxel_set = value
	if is_instance_valid(voxel_set):
		if not voxel_set.is_connected("requested_refresh", self, "update_view"):
			voxel_set.connect("requested_refresh", self, "update_view", [true])
	elif is_instance_valid(Voxels):
		for child in Voxels.get_children():
			Voxels.remove_child(child)
			child.queue_free()
	
	if update:
		update_view(true)


# Returns true if voxel set id is selected
func has_selected(voxel_id : int) -> bool:
	return _selections.has(voxel_id)


func get_selected(index : int) -> int:
	return _selections[index]


func get_selections() -> Array:
	return _selections.duplicate()


func get_selected_size() -> int:
	return _selections.size()


# Returns VoxelButton with given voxel_id if found, else returns null
func get_voxel_button(voxel_id : int):
	return Voxels.find_node(str(voxel_id), false, false) if Voxels else null


# Selects voxel with given voxel_id if found, and emits selected_voxel
func select(voxel_id : int, emit := true) -> void:
	if selection_max == 0:
		return
	
	var voxel_button = get_voxel_button(voxel_id)
	if not is_instance_valid(voxel_button):
		return
	
	unselect_shrink(selection_max - 1, emit)
	
	voxel_button.pressed = true
	_selections.append(voxel_id)
	if emit:
		emit_signal("selected_voxel", voxel_id)


# Unselects voxel with given voxel_id if found, and emits unselected_voxel
func unselect(voxel_id : int, emit := true) -> void:
	var index := _selections.find(voxel_id)
	if index == -1:
		return
	
	_selections.remove(index)
	var voxel_button = get_voxel_button(voxel_id)
	if is_instance_valid(voxel_button):
		voxel_button.pressed = false
	if emit:
		emit_signal("unselected_voxel", voxel_id)


# Unselects all selected voxel ids
func unselect_all(emit := true) -> void:
	while not _selections.empty():
		unselect(_selections[-1], emit)


# Unselects all voxels ids until given size is met
func unselect_shrink(size := selection_max, emit := true) -> void:
	if size >= 0:
		while _selections.size() > size:
			unselect(_selections[-1], emit)


# Updates the listing of voxels
# redraw   :   bool   :   if true will repopulate listing with new Voxel Buttons
func update_view(redraw := false) -> void:
	if is_instance_valid(Voxels) and is_instance_valid(voxel_set):
		if redraw:
			for child in Voxels.get_children():
				Voxels.remove_child(child)
				child.queue_free()
			
			for id in voxel_set.get_ids():
				var voxel_button := VoxelButton.instance()
				voxel_button.name = str(id)
				voxel_button.set_voxel_id(id, false)
				voxel_button.set_voxel_set(voxel_set, false)
				voxel_button.update_view()
				voxel_button.toggle_mode = true
				voxel_button.pressed = _selections.has(id)
				voxel_button.mouse_filter = Control.MOUSE_FILTER_PASS
				voxel_button.connect("pressed", self, "_on_VoxelButton_pressed", [voxel_button])
				Voxels.add_child(voxel_button)
		
		var keys := search.split(",", false)
		for id in voxel_set.get_ids():
			var show = true
			for key in keys:
				if (key.is_valid_integer() and id == key.to_int()) or voxel_set.id_to_name(id).find(key) > -1:
					show = true
					break
				show = false
			
			if not show:
				unselect(id)
			get_voxel_button(id).visible = show
		call_deferred("correct")


# Corrects the columns of listing to fit as many voxels horizonataly
func correct() -> void:
	if is_instance_valid(Voxels):
		Voxels.columns = int(floor(rect_size.x / 36))



## Private Methods
func _on_Voxels_gui_input(event):
	if allow_edit and event is InputEventMouseButton and event.button_index == BUTTON_RIGHT:
		ContextMenu.clear()
		if _selections.size() > 1:
			ContextMenu.add_icon_item(
					preload("res://addons/voxel-core/assets/controls/cancel.png"),
					"Deselect voxels", 3)
			ContextMenu.add_separator()
		ContextMenu.add_icon_item(
					preload("res://addons/voxel-core/assets/controls/add.png"),
					"Add voxel", 0)
		if _selections.size() == 1:
			ContextMenu.add_icon_item(
					preload("res://addons/voxel-core/assets/controls/duplicate.png"),
					"Duplicate voxel", 1)
			ContextMenu.add_icon_item(
					preload("res://addons/voxel-core/assets/controls/sub.png"),
					"Remove voxel", 2)
		elif _selections.size() > 1:
			ContextMenu.add_separator()
			ContextMenu.add_icon_item(
					preload("res://addons/voxel-core/assets/controls/duplicate.png"),
					"Duplicate voxels", 4)
			ContextMenu.add_icon_item(
					preload("res://addons/voxel-core/assets/controls/sub.png"),
					"Remove voxels", 5)
		ContextMenu.set_as_minsize()
		
		ContextMenu.popup(Rect2(event.global_position, ContextMenu.rect_size))


func _on_VoxelButton_pressed(voxel_button) -> void:
	if selection_max != 0:
		if voxel_button.pressed:
			if not Input.is_key_pressed(KEY_CONTROL):
				unselect_all()
			select(voxel_button.voxel_id)
		else:
			if _selections.has(voxel_button.voxel_id):
				unselect_all()
				select(voxel_button.voxel_id)
			else:
				unselect(voxel_button.voxel_id)
	else: voxel_button.pressed = false


func _on_ContextMenu_id_pressed(_id : int):
	match _id:
		0:
			var id = voxel_set.size()
			undo_redo.create_action("VoxelSetViewer : Add voxel")
			undo_redo.add_do_method(voxel_set, "add_voxel", Voxel.colored(Color.white))
			undo_redo.add_undo_method(voxel_set, "erase_voxel", id)
			undo_redo.add_do_method(voxel_set, "request_refresh")
			undo_redo.add_undo_method(voxel_set, "request_refresh")
			undo_redo.commit_action()
			unselect_all()
			select(id)
		1:
			var id = voxel_set.size()
			undo_redo.create_action("VoxelSetViewer : Duplicate voxel")
			undo_redo.add_do_method(
					voxel_set,
					"add_voxel",
					voxel_set.get_voxel(_selections[0]).duplicate(true))
			undo_redo.add_undo_method(voxel_set, "erase_voxel", id)
			undo_redo.add_do_method(voxel_set, "request_refresh")
			undo_redo.add_undo_method(voxel_set, "request_refresh")
			undo_redo.commit_action()
			unselect_all()
			select(id)
		2:
			undo_redo.create_action("VoxelSetViewer : Remove voxel")
			undo_redo.add_do_method(voxel_set, "erase_voxel", _selections[0])
			undo_redo.add_undo_method(
					voxel_set,
					"insert_voxel",
					_selections[0],
					voxel_set.get_voxel(_selections[0]))
			undo_redo.add_do_method(voxel_set, "request_refresh")
			undo_redo.add_undo_method(voxel_set, "request_refresh")
			undo_redo.commit_action()
			unselect(_selections[0])
		3: unselect_all()
		4:
			undo_redo.create_action("VoxelSetViewer : Duplicate voxels")
			var id = voxel_set.size()
			var ids = []
			for selection in range(_selections.size()):
				ids.append(id + selection)
				undo_redo.add_do_method(
						voxel_set,
						"add_voxel",
						voxel_set.get_voxel(_selections[selection]).duplicate(true))
				undo_redo.add_undo_method(
					voxel_set,
					"erase_voxel",
					id + _selections.size() - selection - 1)
			undo_redo.add_do_method(voxel_set, "request_refresh")
			undo_redo.add_undo_method(voxel_set, "request_refresh")
			undo_redo.commit_action()
			unselect_all()
			for _id in ids:
				select(_id)
		5: 
			undo_redo.create_action("VoxelSetViewer : Remove voxels")
			var selections := _selections.duplicate()
			selections.sort()
			for index in range(selections.size()):
				undo_redo.add_do_method(
						voxel_set,
						"erase_voxel",
						selections[selections.size() - index - 1])
				undo_redo.add_undo_method(
						voxel_set,
						"insert_voxel",
						selections[index],
						voxel_set.get_voxel(selections[index]))
			undo_redo.add_do_method(voxel_set, "request_refresh")
			undo_redo.add_undo_method(voxel_set, "request_refresh")
			undo_redo.commit_action()
			unselect_all()


func _on_Search_text_changed(new_text):
	search = new_text
	update_view()