diff options
Diffstat (limited to 'addons/voxel-core/controls/voxel_set_viewer')
-rw-r--r-- | addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd | 368 | ||||
-rw-r--r-- | addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn | 81 |
2 files changed, 449 insertions, 0 deletions
diff --git a/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd new file mode 100644 index 0000000..20d2ae3 --- /dev/null +++ b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd @@ -0,0 +1,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() diff --git a/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn new file mode 100644 index 0000000..5bf8caf --- /dev/null +++ b/addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=6 format=2] + +[ext_resource path="res://addons/voxel-core/controls/voxel_set_viewer/voxel_set_viewer.gd" type="Script" id=2] +[ext_resource path="res://addons/voxel-core/assets/controls/duplicate.png" type="Texture" id=3] +[ext_resource path="res://addons/voxel-core/assets/controls/sub.png" type="Texture" id=4] +[ext_resource path="res://addons/voxel-core/assets/controls/add.png" type="Texture" id=5] +[ext_resource path="res://addons/voxel-core/assets/controls/cancel.png" type="Texture" id=6] + +[node name="VoxelSetViewer" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 2 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Search" type="LineEdit" parent="VBoxContainer"] +margin_right = 1024.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +clear_button_enabled = true +placeholder_text = "Search by ID or Name..." +caret_blink = true +caret_blink_speed = 0.5 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] +margin_top = 28.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +follow_focus = true + +[node name="Voxels" type="GridContainer" parent="VBoxContainer/ScrollContainer"] +margin_right = 1024.0 +margin_bottom = 572.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/hseparation = 4 +columns = 28 + +[node name="Hints" type="VBoxContainer" parent="VBoxContainer"] +visible = false +margin_top = 582.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +custom_constants/separation = 0 + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/Hints"] +margin_right = 1024.0 +margin_bottom = 4.0 + +[node name="Hint" type="Label" parent="VBoxContainer/Hints"] +margin_top = 4.0 +margin_right = 1024.0 +margin_bottom = 18.0 +size_flags_horizontal = 3 +size_flags_vertical = 7 +align = 2 +valign = 1 +autowrap = true + +[node name="ContextMenu" type="PopupMenu" parent="."] +margin_right = 146.0 +margin_bottom = 154.0 +items = [ "Add voxel", ExtResource( 5 ), 0, false, false, 0, 0, null, "", false, "Duplicate voxel", ExtResource( 3 ), 0, false, false, 2, 0, null, "", false, "Remove voxel", ExtResource( 4 ), 0, false, false, 2, 0, null, "", false, "", null, 0, false, false, 3, 0, null, "", true, "Deselect", ExtResource( 6 ), 0, false, false, 4, 0, null, "", false, "Duplicate voxels", ExtResource( 3 ), 0, false, false, 5, 0, null, "", false, "Remove voxels", ExtResource( 4 ), 0, false, false, 6, 0, null, "", false ] +__meta__ = { +"_edit_use_anchors_": false +} +[connection signal="resized" from="." to="." method="correct"] +[connection signal="text_changed" from="VBoxContainer/Search" to="." method="_on_Search_text_changed"] +[connection signal="gui_input" from="VBoxContainer/ScrollContainer/Voxels" to="." method="_on_Voxels_gui_input"] +[connection signal="id_pressed" from="ContextMenu" to="." method="_on_ContextMenu_id_pressed"] |