@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