miggor-StreamGraph/graph_node_renderer/variable_viewer.gd

304 lines
9.2 KiB
GDScript3
Raw Permalink Normal View History

# (c) 2023-present Eroax
# (c) 2023-present Yagich
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
extends VBoxContainer
class_name VariableViewer
const REMOVE_ICON = preload("res://graph_node_renderer/textures/remove-icon.svg")
@onready var new_variable_button: Button = %NewVariableButton
@onready var variable_tree: Tree = %VariableTree
@onready var types_popup: PopupMenu = %TypesPopup
var root: TreeItem
var _old_name: String
signal top_field_edited(old_name: String, new_name: String, new_value: Variant)
signal top_field_removed(field_name: String)
func _ready() -> void:
variable_tree.set_column_title(0, "Name")
variable_tree.set_column_title(1, "Value")
variable_tree.set_column_title(2, "Type")
variable_tree.set_column_expand(1, true)
variable_tree.set_column_expand_ratio(0, 15)
variable_tree.set_column_expand_ratio(1, 70)
variable_tree.set_column_expand_ratio(2, 15)
root = variable_tree.create_item()
rebuild_variable_tree()
variable_tree.item_activated.connect(edit_item)
variable_tree.item_edited.connect(commit_item_change)
variable_tree.nothing_selected.connect(
func():
var item := variable_tree.get_edited()
if item == null:
item = variable_tree.get_selected()
if item != null:
commit_item_change(item)
#item.deselect(variable_tree.get_selected_column())
for i in variable_tree.get_columns():
item.deselect(i)
)
variable_tree.button_clicked.connect(_on_variable_tree_button_clicked)
for i in DeckType.Types.size() - 1:
types_popup.add_radio_check_item(DeckType.type_str(i))
types_popup.id_pressed.connect(_on_types_popup_id_pressed)
new_variable_button.pressed.connect(_on_new_variable_button_pressed)
disable_new_button()
func commit_item_change(item: TreeItem = variable_tree.get_edited()) -> void:
var new_name := item.get_text(0)
var new_value
var type: DeckType.Types = item.get_metadata(2)
match type:
DeckType.Types.STRING:
new_value = item.get_text(1)
DeckType.Types.BOOL:
new_value = item.is_checked(1)
DeckType.Types.NUMERIC:
new_value = item.get_range(1)
_:
new_value = item.get_meta(&"value")
if not item.has_meta(&"container"):
top_field_edited.emit(_old_name, new_name, new_value)
else:
var container = item.get_meta(&"container")
# array
if item.get_meta(&"indexed", false):
var index := item.get_index()
(container as Array)[index] = new_value
# dictionary
else:
var key := new_name
(container as Dictionary).erase(_old_name)
(container as Dictionary)[key] = new_value
set_item_value(item, new_value)
func edit_item() -> void:
var column := variable_tree.get_selected_column()
var item := variable_tree.get_selected()
# do nothing if this is an array and user is trying to edit index
# (TODO: proper reordering)
if column == 0 and item.get_meta(&"indexed"):
return
if column < 2:
var value = item.get_meta(&"value")
if column == 1 and (value is Array or value is Dictionary):
item.collapsed = not item.collapsed
return
_old_name = item.get_text(0)
variable_tree.edit_selected(true)
elif column == 2:
var pos := get_global_mouse_position()
var r := Rect2i(Vector2i(pos), Vector2i(0, 100))
for i in types_popup.get_item_count():
types_popup.set_item_checked(i, false)
types_popup.set_item_checked(item.get_metadata(2), true)
types_popup.popup(r)
func rebuild_variable_tree(data: Dictionary = {}) -> void:
#variable_tree.clear() # godot will raw dog a nullptr later if we clear the whole tree
for i in root.get_children():
i.free()
for i in data:
add_item(i, data[i])
func add_item(item_name: String, item_value: Variant, parent: TreeItem = root, container: Variant = null, indexed: bool = false) -> TreeItem:
# the container parameter is -1 instead of null because Object#get_meta() logs an error
# if the default parameter is null
var item := variable_tree.create_item(parent)
item.set_text(0, item_name)
# TODO: proper null handling
var value_type := typeof(item_value)
var type: DeckType.Types
if value_type == TYPE_NIL:
type = DeckType.Types.STRING
item_value = "<nothing>"
else:
type = DeckType.INVERSE_GODOT_TYPES_MAP[typeof(item_value)]
item.set_meta(&"container", container)
item.set_meta(&"indexed", indexed)
set_item_value(item, item_value)
# TODO: TreeItem has a barely-documented quirk that can make the RANGE cell mode
# into a dropdown if the item text is comma-separated values, so use that
item.set_text(2, DeckType.type_str(type))
item.set_metadata(2, type)
if item_value is Dictionary:
for i in item_value:
add_item(i, item_value[i], item, item_value)
if item_value is Array:
for i in (item_value as Array).size():
add_item(str(i), item_value[i], item, item_value, true)
#item.collapsed = true
item.add_button(2, REMOVE_ICON)
return item
func set_item_value(item: TreeItem, value: Variant) -> void:
item.set_meta(&"value", value)
var type: DeckType.Types = DeckType.INVERSE_GODOT_TYPES_MAP[typeof(value)]
match type:
DeckType.Types.NUMERIC:
item.set_cell_mode(1, TreeItem.CELL_MODE_RANGE)
item.set_range_config(1, -9999, 9999, 0.0001)
item.set_range(1, value)
DeckType.Types.BOOL:
item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
item.set_checked(1, value)
_:
item.set_cell_mode(1, TreeItem.CELL_MODE_STRING)
item.set_text(1, str(value))
if item.has_meta(&"container"):
var container = item.get_meta(&"container")
if container is Array:
var index := item.get_index()
container[index] = value
else:
var key := item.get_text(0)
container[key] = value
if item.get_parent() != root:
refresh_item_value(item.get_parent())
func refresh_item_value(item: TreeItem) -> void:
var value = item.get_meta(&"value")
var type: DeckType.Types = DeckType.INVERSE_GODOT_TYPES_MAP[typeof(value)]
match type:
DeckType.Types.NUMERIC:
item.set_cell_mode(1, TreeItem.CELL_MODE_RANGE)
item.set_range_config(1, -9999, 9999, 0.0001)
item.set_range(1, value)
DeckType.Types.BOOL:
item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
item.set_checked(1, value)
_:
item.set_cell_mode(1, TreeItem.CELL_MODE_STRING)
item.set_text(1, str(value))
func _on_variable_tree_button_clicked(item: TreeItem, _column: int, _id: int, mouse_button_index: int) -> void:
if mouse_button_index != MOUSE_BUTTON_LEFT:
return
# we only have a delete button for now, so assume it is what's clicked
if not item.has_meta(&"container"):
var key := item.get_text(0)
top_field_removed.emit(key)
item.free()
else:
var container = item.get_meta(&"container")
var parent := item.get_parent()
# array
if item.get_meta(&"indexed", false):
var index := item.get_index()
(container as Array).remove_at(index)
item.free()
# go through the array and reset the index strings
for i in (container as Array).size():
parent.get_child(i).set_text(0, str(i))
# dictionary
else:
(container as Dictionary).erase(item.get_text(0))
item.free()
refresh_item_value(parent)
func _on_types_popup_id_pressed(id: int) -> void:
var current_item := variable_tree.get_selected()
if current_item.get_metadata(2) == id:
return
current_item.set_metadata(2, id)
var new_value: Variant
match id as DeckType.Types:
DeckType.Types.BOOL, DeckType.Types.STRING, DeckType.Types.NUMERIC:
# simple types that can generally be converted between each other
var target_type: Variant.Type = DeckType.GODOT_TYPES_MAP[id]
new_value = type_convert(current_item.get_meta(&"value"), target_type)
DeckType.Types.ARRAY:
new_value = []
for i in current_item.get_children():
i.free()
DeckType.Types.DICTIONARY:
new_value = {}
for i in current_item.get_children():
i.free()
set_item_value(current_item, new_value)
current_item.set_text(2, DeckType.type_str(id))
if not current_item.has_meta(&"container"):
var field_name := current_item.get_text(0)
top_field_edited.emit(field_name, field_name, new_value)
func _on_new_variable_button_pressed() -> void:
var selected := variable_tree.get_selected()
# TODO: UX impr. - if selected is part of a container, add to that container instead
# (but if selected is a container, prioritize adding to that)
if selected == null or not (selected.get_meta(&"value") is Array or selected.get_meta(&"value") is Dictionary):
# top field
var var_name := "new_variable%s" % variable_tree.get_root().get_child_count()
var new_item := add_item(var_name, "")
top_field_edited.emit(var_name, var_name, "")
new_item.select(0)
else:
var container = selected.get_meta(&"value")
if container is Dictionary:
var var_name := "new_key%s" % (container as Dictionary).size()
var new_item := add_item(var_name, "", selected, container)
new_item.select(0)
container[var_name] = ""
else:
var index := (container as Array).size()
(container as Array).append("")
var new_item := add_item(str(index), "", selected, container, true)
new_item.select(0)
refresh_item_value(selected)
func enable_new_button() -> void:
new_variable_button.disabled = false
new_variable_button.tooltip_text = ""
func disable_new_button() -> void:
new_variable_button.disabled = true
new_variable_button.tooltip_text = "There is no deck open, so you can not create a new variable.\nOpen a deck first."