417 lines
13 KiB
GDScript
417 lines
13 KiB
GDScript
@tool
|
||
extends GridContainer
|
||
|
||
@export var _selector_color: Color = Color(1.0, 1.0, 1.0, 1.0)
|
||
@export var columns_count: int = 1: set = _set_columns_count
|
||
@export var rows_count: int = 1: set = _set_rows_count
|
||
@export var node_type_file: String = 'res://ячейка-1.tscn'
|
||
const Selector = preload('res://Selector.tscn')
|
||
const INDEX_FORMAT = '%02d_%02d'
|
||
const NODE_PREFIX = 'NODE_'
|
||
const DATA_PREFIX = 'DATA_'
|
||
const CONN_PREFIX = 'CONN_'
|
||
const SLCT_PREFIX = 'SLCT_'
|
||
|
||
const NODE_KEY_FORMAT = NODE_PREFIX + INDEX_FORMAT
|
||
const DATA_KEY_FORMAT = DATA_PREFIX + INDEX_FORMAT
|
||
const CONNECTED_KEY_FORMAT = CONN_PREFIX + INDEX_FORMAT
|
||
const ROW_SELECTOR_KEY_FORMAT = SLCT_PREFIX + INDEX_FORMAT
|
||
|
||
var nodes: = {}
|
||
var selector_color_proc: Callable
|
||
var selector_size_proc: Callable
|
||
signal row_pressed(i_row) # Вызывается при нажатии кнопкой мыши на строку
|
||
const MAX_INDEX = 99 # Максимальное значение индекса для колонки и строки. Связано с форматом ключа
|
||
|
||
|
||
func align_to_left_bottom(header, gap: = Vector2(0, 0)):
|
||
""" Выравнивает по нижнему левому углу. Используется для выравнивания по заголовку """
|
||
position.x = header.position.x + gap.x
|
||
position.y = header.position.y + header.size.y + gap.y
|
||
|
||
|
||
func _assert_meta(i_col: int, i_row: int, pname: String, node):
|
||
assert(node.has_meta(pname)) #,'ошибка: нет мета-свойства \"%s\" у ячейки (%s): колонка:%d ряд:%d' % [pname, typeof(node), i_col, i_row])
|
||
|
||
|
||
func _set_columns_count(cc: int):
|
||
columns_count = cc
|
||
|
||
|
||
func _set_rows_count(rc: int):
|
||
rows_count = rc
|
||
|
||
|
||
func set_node_meta(i_col: int, i_row: int, pname: String, val) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
node.set_meta(pname, val)
|
||
|
||
|
||
func get_node_meta(i_col: int, i_row: int, pname: String):
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
_assert_meta(i_col, i_row, pname, node)
|
||
return node.get_meta(pname)
|
||
|
||
|
||
func set_node_property(i_col: int, i_row: int, pname: String, val) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
node.set(pname, val)
|
||
|
||
|
||
func get_node_property(i_col: int, i_row: int, pname: String):
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
return node.get(pname)
|
||
|
||
|
||
func get_node_key(format: String, i_col: int, i_row: int) -> String:
|
||
var key: String = format % [i_row, i_col]
|
||
assert(nodes.has(key)) #,'ошибка: нет такой ячейки: колонка:%d ряд:%d' % [i_col, i_row])
|
||
return key
|
||
|
||
|
||
func get_key_indexes(key: String) -> Array:
|
||
var tokens: = key.split('_')
|
||
assert(tokens.size() == 3) #,'ошибка: неверный ключ: \"%s\"' % key)
|
||
return [int(tokens[2]), int(tokens[1])]
|
||
|
||
|
||
func set_columns_min_size(sizes: Array) -> void:
|
||
for i_row in get_rows_count():
|
||
set_nodes_min_size(i_row, sizes)
|
||
|
||
|
||
func set_columns_aligment(alignments: Array) -> void:
|
||
for i_row in get_rows_count():
|
||
for i_col in alignments.size():
|
||
set_node_alligment(i_col, i_row, alignments[i_col])
|
||
|
||
|
||
func _input(event) -> void:
|
||
if event is InputEventMouseButton:
|
||
if not event.pressed:
|
||
return
|
||
if event.position.x < position.x:
|
||
emit_signal('focus_exited')
|
||
return
|
||
if event.position.x > (position.x + size.x):
|
||
emit_signal('focus_exited')
|
||
return
|
||
if event.position.y < position.y:
|
||
emit_signal('focus_exited')
|
||
return
|
||
if event.position.y > (position.y + size.y):
|
||
emit_signal('focus_exited')
|
||
return
|
||
var rows_count: = get_rows_count()
|
||
var parent = get_parent()
|
||
var y0 = parent.position.y
|
||
for i_row in rows_count:
|
||
var rect = [0, 0, 0, 0]
|
||
get_node_rect(0, i_row, rect)
|
||
var y = rect[1] + position.y + y0
|
||
if event.position.y < y:
|
||
continue
|
||
if event.position.y > (y + rect[3]):
|
||
continue
|
||
emit_signal('row_pressed', i_row)
|
||
break
|
||
|
||
|
||
func _init() -> void:
|
||
selector_color_proc = Callable(self, 'default_selector_color_proc')
|
||
assert(selector_color_proc.is_valid())
|
||
selector_size_proc = Callable(self, 'default_selector_size_proc')
|
||
assert(selector_size_proc.is_valid())
|
||
|
||
|
||
func default_selector_color_proc(i_row: int):
|
||
return _selector_color
|
||
|
||
|
||
func set_selector_color_proc(instance: Object, function: String) -> void:
|
||
selector_color_proc = Callable(instance, function)
|
||
assert(selector_color_proc.is_valid())
|
||
|
||
|
||
func set_selector_size_proc(instance: Object, function: String) -> void:
|
||
selector_size_proc = Callable(instance, function)
|
||
assert(selector_size_proc.is_valid())
|
||
|
||
|
||
func default_selector_size_proc(i_row: int, rect: Array) -> void:
|
||
get_node_rect(0, i_row, rect)
|
||
rect[0] = rect[0] + position.x - 1
|
||
rect[1] = rect[1] + position.y - 4
|
||
rect[2] = size.x + 2
|
||
rect[3] = rect[3] + 8
|
||
|
||
|
||
func clear_rows_selected():
|
||
var rows_count: = get_rows_count()
|
||
for i_row in rows_count:
|
||
set_row_selected(i_row, false)
|
||
|
||
|
||
func set_row_selected(i_row: int, selected: bool = true) -> void:
|
||
var key: = ROW_SELECTOR_KEY_FORMAT % [i_row, 0]
|
||
var parent = get_parent()
|
||
if nodes.has(key) and (not selected):
|
||
var selector = nodes[key]
|
||
parent.remove_child(selector)
|
||
selector.queue_free()
|
||
nodes.erase(key)
|
||
elif (not nodes.has(key)) and selected:
|
||
var selector = Selector.instantiate()
|
||
var rect: = [0, 0, 0, 0]
|
||
selector_size_proc.call(i_row, rect)
|
||
selector.position.x = rect[0]
|
||
selector.position.y = rect[1]
|
||
selector.size.x = rect[2]
|
||
selector.size.y = rect[3]
|
||
selector.modulate = selector_color_proc.call(i_row)
|
||
nodes[key] = selector
|
||
parent.add_child(selector)
|
||
|
||
|
||
func is_row_selected(i_row: int) -> bool:
|
||
var key: = get_node_key(ROW_SELECTOR_KEY_FORMAT, 0, i_row)
|
||
return nodes.has(key)
|
||
|
||
|
||
func get_selected_rows(rows: Array) -> void:
|
||
for key in nodes.keys():
|
||
if SLCT_PREFIX in key:
|
||
var indexes = get_key_indexes(key)
|
||
rows.append(indexes[1])
|
||
|
||
|
||
func get_rows_count() -> int:
|
||
var rows_count: int = 0
|
||
for key in nodes:
|
||
rows_count += int(NODE_PREFIX in key)
|
||
return rows_count / columns
|
||
|
||
|
||
func get_node2(i_col: int, i_row: int) -> Node:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
return nodes[key]
|
||
|
||
|
||
func set_node_user_data(i_col: int, i_row: int, user_data: Object) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key] = user_data
|
||
|
||
|
||
func add_row(row_types: Array) -> void:
|
||
assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1))
|
||
set_columns(row_types.size())
|
||
var rows_count: int = get_rows_count()
|
||
assert(rows_count < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1))
|
||
for i_col in columns:
|
||
var node = row_types[i_col].instantiate()
|
||
var key: String = NODE_KEY_FORMAT % [rows_count, i_col]
|
||
node.name = key
|
||
add_child(node)
|
||
nodes[key] = node
|
||
|
||
|
||
func get_node_user_data(i_col: int, i_row: int) -> Object:
|
||
var key: = get_node_key(DATA_KEY_FORMAT, i_col, i_row)
|
||
return nodes[key]
|
||
|
||
|
||
func remove_row(i_row: int):
|
||
|
||
var sel_key: String = ROW_SELECTOR_KEY_FORMAT % [i_row, 0]
|
||
if nodes.has(sel_key):
|
||
var selector = nodes[sel_key]
|
||
get_parent().remove_child(selector)
|
||
selector.queue_free()
|
||
nodes.erase(sel_key)
|
||
for i_col in columns:
|
||
var node_key: String = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var conn_key: String = CONNECTED_KEY_FORMAT % [i_row, i_col]
|
||
var data_key: String = DATA_KEY_FORMAT % [i_row, i_col]
|
||
var node = nodes[node_key]
|
||
nodes.erase(node_key)
|
||
remove_child(node)
|
||
node.queue_free()
|
||
|
||
if nodes.has(data_key):
|
||
nodes.erase(data_key)
|
||
if nodes.has(conn_key):
|
||
var conn = nodes[conn_key]
|
||
var callback = conn[0]
|
||
var dest_node = conn[1]
|
||
node.disconnect('pressed', Callable(dest_node, callback))
|
||
nodes.erase(conn_key)
|
||
|
||
|
||
func rehash(swaps: Array):
|
||
""" Переназначает ключи в соответствии с размерами таблицы.
|
||
После вызова swaps содержит массив пар [<прежний ключ>, <текущий ключ>] """
|
||
assert(not swaps.size()) #,'ошибка: swaps не пустой')
|
||
var nodes_copy = {}
|
||
var i_col = 0
|
||
var i_row = 0
|
||
for key0 in nodes:
|
||
if NODE_PREFIX in key0:
|
||
var node_key1: String = NODE_KEY_FORMAT % [i_row, i_col]
|
||
nodes_copy[node_key1] = nodes[key0]
|
||
swaps.append([key0, node_key1])
|
||
|
||
var indxs = get_key_indexes(key0)
|
||
|
||
var slct_key0: String = ROW_SELECTOR_KEY_FORMAT % [indxs[1], indxs[0]]
|
||
if nodes.has(slct_key0):
|
||
var slct_key1: String = ROW_SELECTOR_KEY_FORMAT % [i_row, i_col]
|
||
nodes_copy[slct_key1] = nodes[slct_key0]
|
||
|
||
var conn_key0: String = CONNECTED_KEY_FORMAT % [indxs[1], indxs[0]]
|
||
if nodes.has(conn_key0):
|
||
var conn_key1: String = CONNECTED_KEY_FORMAT % [i_row, i_col]
|
||
nodes_copy[conn_key1] = nodes[conn_key0]
|
||
|
||
var data_key0: String = DATA_KEY_FORMAT % [indxs[1], indxs[0]]
|
||
if nodes.has(data_key0):
|
||
var data_key1: String = DATA_KEY_FORMAT % [i_row, i_col]
|
||
nodes_copy[data_key1] = nodes[data_key0]
|
||
|
||
i_col += 1
|
||
if i_col == columns:
|
||
i_col = 0
|
||
i_row += 1
|
||
nodes.clear()
|
||
nodes.merge(nodes_copy)
|
||
|
||
|
||
func clear_rows() -> void:
|
||
""" Удаляет все строки таблицы """
|
||
var rows_count: int = get_rows_count()
|
||
for i_row in rows_count:
|
||
remove_row(i_row)
|
||
|
||
|
||
func set_callbacks(dest_node: Node, event_name: String, callback: String, clmns: Array, user_data: Array) -> void:
|
||
""" Настраивает обратные вызовы для колонок.
|
||
:dest_node: Получатель события
|
||
:event_name: Имя события
|
||
:callback: Имя функции получателя события
|
||
:clmns: Индексы колонок
|
||
:user_data: Аргументы функции получателя события """
|
||
var rows_count = get_rows_count()
|
||
for i_row in rows_count:
|
||
for i_col in clmns:
|
||
assert(i_col >= 0 and i_col < columns) #,'ошибка: нет колонки %d' % i_col)
|
||
var node_key: String = NODE_KEY_FORMAT % [i_row, i_col]
|
||
var conn_key: String = CONNECTED_KEY_FORMAT % [i_row, i_col]
|
||
var node = nodes[node_key]
|
||
if nodes.has(conn_key):
|
||
var conn = nodes[conn_key]
|
||
var prev_callback = conn[0]
|
||
var prev_dest_node = conn[1]
|
||
node.disconnect(event_name, Callable(prev_dest_node, prev_callback))
|
||
assert (OK == node.connect(event_name, Callable(dest_node, callback).bind([node] + user_data)))
|
||
nodes[conn_key] = [callback, dest_node]
|
||
|
||
|
||
func set_rows_text(rows: Array) -> void:
|
||
for i_row in rows.size():
|
||
for i_col in columns:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
node.text = rows[i_row][i_col]
|
||
|
||
|
||
func set_nodes_min_size(i_row: int, sizes: Array) -> void:
|
||
for i_col in columns:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
node.custom_minimum_size.x = sizes[i_col]
|
||
node.size.x = sizes[i_col]
|
||
|
||
|
||
func set_node_text(i_col: int, i_row: int, val: String) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key].text = val
|
||
|
||
|
||
func set_rows_state(rows: Array) -> void:
|
||
for i_row in rows.size():
|
||
for i_col in columns:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[name].button_pressed = rows[i_row][i_col]
|
||
|
||
|
||
func set_node_state(i_col: int, i_row: int, val: bool) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key].button_pressed = val
|
||
|
||
|
||
func set_node_frame(i_col: int, i_row: int, name: String, val: int) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var node = nodes[key]
|
||
var sprite = node.get_node(name)
|
||
sprite.frame = val
|
||
|
||
|
||
func set_node_editable(i_col: int, i_row: int, val: bool) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key].editable = val
|
||
|
||
|
||
func set_node_alligment(i_col: int, i_row: int, val: int) -> void:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key].align = val
|
||
|
||
|
||
func set_row_alligments(i_row: int, aligns: Array) -> void:
|
||
for i_col in columns:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key].align = aligns[i_col]
|
||
|
||
|
||
func get_row_text(i_row: int) -> Array:
|
||
var row: = []
|
||
for i_col in columns:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
row.append(nodes[key].text)
|
||
return row
|
||
|
||
|
||
func set_row_text(i_row: int, row: Array, i_begin: int = 0) -> void:
|
||
for i_col in row.size():
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
nodes[key].text = row[i_col]
|
||
|
||
|
||
func get_rows_text() -> Array:
|
||
var rows_count: = get_rows_count()
|
||
var rows: = []
|
||
for i_row in rows_count:
|
||
var row = get_row_text(i_row)
|
||
rows.append(row)
|
||
return rows
|
||
|
||
|
||
func get_row(i_row: int) -> Array:
|
||
var row: = []
|
||
for i_col in columns:
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
row.append(nodes[key])
|
||
return row
|
||
|
||
|
||
func get_node_rect(i_col: int, i_row: int, rect: Array) -> void:
|
||
assert(rect.size() >= 4) #,'ошибка: мало элементов для возврата результата')
|
||
var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row)
|
||
var cell = nodes[key]
|
||
rect[0] = cell.position.x
|
||
rect[1] = cell.position.y
|
||
rect[2] = cell.size.x
|
||
rect[3] = cell.size.y
|