Доработка. Переход на новую таблицу.

This commit is contained in:
MaD_CaT
2023-08-15 13:46:46 +03:00
parent b1ef6595ed
commit b24b362e0c
16 changed files with 1055 additions and 839 deletions

12
table/header.tscn Normal file
View File

@@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://ch14g46swx74h"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2thpi"]
bg_color = Color(0.8, 0.533333, 0.27451, 1)
[node name="header" type="Label"]
offset_right = 128.0
offset_bottom = 23.0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_styles/normal = SubResource("StyleBoxFlat_2thpi")
horizontal_alignment = 1
vertical_alignment = 1

BIN
table/nine-patch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bw3yv3smvaxqn"
path="res://.godot/imported/nine-patch.png-798f7950fb3126b4e1ed0c39d9e4beba.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://table/nine-patch.png"
dest_files=["res://.godot/imported/nine-patch.png-798f7950fb3126b4e1ed0c39d9e4beba.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

10
table/node.tscn Normal file
View File

@@ -0,0 +1,10 @@
[gd_scene format=3 uid="uid://c1i4rwaajq7d5"]
[node name="Label" type="LineEdit"]
offset_right = 121.0
offset_bottom = 23.0
theme_override_colors/font_color = Color(1, 0.596078, 0.4, 1)
editable = false
context_menu_enabled = false
caret_blink = true
caret_blink_interval = 0.1

15
table/selector.tscn Normal file
View File

@@ -0,0 +1,15 @@
[gd_scene load_steps=2 format=3 uid="uid://pd3fcx55duj2"]
[ext_resource type="Texture2D" uid="uid://bw3yv3smvaxqn" path="res://table/nine-patch.png" id="1_ik10p"]
[node name="NinePatchRect" type="NinePatchRect"]
offset_right = 17.0
offset_bottom = 16.0
texture = ExtResource("1_ik10p")
draw_center = false
patch_margin_left = 8
patch_margin_top = 8
patch_margin_right = 8
patch_margin_bottom = 8
axis_stretch_horizontal = 1
axis_stretch_vertical = 1

594
table/table.gd Normal file
View File

@@ -0,0 +1,594 @@
extends GridContainer
## Виджет "Таблица"
@export var node_gap: = Vector2(0.0, 0.0) ## Зазор между ячейками, который не возможно вычислить или получить
@export var selector_color: = Color(1.0, 1.0, 1.0, 1.0) ## Окрашивающий цвет для индикатора выбранного ряда
@export var selector_key_down: Key = KEY_DOWN ## Клавиша увеличения индекса выбранного ряда
@export var selector_key_up: Key = KEY_UP ## Клавиша уменьшения индекса выбранного ряда
@export_file var node_type_file: = 'res://table/node.tscn': get = get_node_scene, set = load_node_scene ## Сцена ячейки устанавливаемая по умолчанию
@export_file var selector_type_file: = 'res://table/selector.tscn': set = load_selector_scene ## Сцена индикатора для выбранного ряда
const HEAD_PREFIX = 'HEAD_' ## Префикс ключа заголовка таблицы
const INDEX_FORMAT = '%02d_%02d' ## Формат индекса ключа
const MAX_INDEX = 99 ## Максимальное значение индекса для колонки и строки. Связано с форматом индекса ключа
const NODE_PREFIX = 'NODE_' ## Префикс ключа ячейки таблицы
const SLCT_PREFIX = 'SLCT_' ## Префикс ключа индикатора выбранного ряда таблицы
const HEAD_KEY_FORMAT = HEAD_PREFIX + INDEX_FORMAT ## Формат ключа заголовка
const NODE_KEY_FORMAT = NODE_PREFIX + INDEX_FORMAT ## Формат ключа ячейки
const ROW_SELECTOR_KEY_FORMAT = SLCT_PREFIX + INDEX_FORMAT ## Формат ключа индикаторов выбранного ряда
const META_NAME_USER_DATA = 'user_data'
const META_NAME_CALLEBLE = 'calleble'
var header: GridContainer ## Заголовок таблицы
var in_focus: = false ## Фокус пользовательского ввода
var nodes: = {} ## Все ячейки таблицы и дополнительные элементы
var ScnNode: PackedScene ## Прототип ячейки таблицы
var ScnSelector: PackedScene ## Прототип индикатора выбранного ряда
var selector_color_proc: Callable ## Ссылка на процедуру генератора цвета индикатора выбранного ряда
var selector_size_proc: Callable ## Ссылка на процедуру генератора размера индикатора выбранного ряда
signal row_pressed(i_row: int) ## Вызывается при нажатии кнопкой мыши на строку
signal selected_changed(i_row: int) ## Вызывается при изменении выбранного ряда. i_row - текущий выбранный ряд
var scroll: Control
var scroll_adj: = Vector2(0, 0)
func get_node_scene(): return node_type_file
func _assert_meta(i_col: int, i_row: int, pname: String, node): assert(node.has_meta(pname), 'ошибка: нет мета-свойства \"%s\" у ячейки колонка:%d ряд:%d' % [pname, i_col, i_row])
func get_node_user_data(i_col: int, i_row: int): return get_node_meta(i_col, i_row, META_NAME_USER_DATA)
func set_node_user_data(i_col: int, i_row: int, val) -> void: set_node_meta(i_col, i_row, META_NAME_USER_DATA, val)
## Закрепляет заголовок таблицы. [param scrl] - Прокрутка,
## [param prnt] - Родитель прокрутки заданный в редакторе
func pin_header(scrl: ScrollContainer, prnt: Control):
scrl.remove_child(header)
prnt.add_child(header)
## TODO: Это значение следует получить из темы
var v_separation: = 3.0
header.position = scrl.position - Vector2(0, header.size.y + v_separation)
scroll = scrl
scroll_adj = scrl.position
## Перемещает указатель на одну строку вверх
func selector_up():
var i_rows: = []
get_selected_rows(i_rows)
for i in i_rows:
set_row_selected(i, false)
var rows_count: = get_rows_count()
if not (i_rows.size() and (rows_count > 1)):
return
var i_row = i_rows[0]
i_row -= 1
if i_row < 0:
i_row = rows_count - 1
set_row_selected(i_row, true)
emit_signal('selected_changed', i_row)
## Перемещает указатель на одну строку вниз
func selector_down():
var i_rows: = []
get_selected_rows(i_rows)
for i in i_rows:
set_row_selected(i, false)
var rows_count: = get_rows_count()
if not (i_rows.size() and (rows_count > 1)):
return
var i_row = i_rows[0]
i_row += 1
i_row %= rows_count
set_row_selected(i_row, true)
emit_signal('selected_changed', i_row)
##
func set_columns_alignments(alignments):
assert(columns == alignments.size())
var rows_count: = get_rows_count()
for i_row in rows_count:
for i_col in alignments.size():
set_node_alligment(i_col, i_row, alignments[i_col])
##
func load_selector_scene(file_name: String):
selector_type_file = file_name
ScnSelector = load(file_name)
##
func load_node_scene(file_name: String):
node_type_file = file_name
ScnNode = load(file_name)
##
func _ready():
ScnSelector = load(selector_type_file)
ScnNode = load(node_type_file)
assert(ScnSelector)
assert(ScnNode)
## Выравнивает по нижнему левому углу. Используется для выравнивания по заголовку
func align_to_left_bottompos(hdr: Control, gap: = Vector2(0, 0)):
position.x = hdr.position.x + gap.x
position.y = hdr.position.y + hdr.size.y + gap.y
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
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)
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
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)
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
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)
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
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)
## [param i_col] - индекс колонки. [param i_row] - индекс ряда
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:
var rows_count: = get_rows_count()
for i_row in rows_count:
set_nodes_min_size(i_row, sizes)
set_nodes_min_size(0, sizes, HEAD_KEY_FORMAT)
##
func set_columns_aligment(alignments: Array) -> void:
var rows_count: = get_rows_count()
for i_row in 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 InputEventKey:
if in_focus and event.pressed:
if event.keycode == selector_key_up:
selector_up()
elif event.keycode == selector_key_down:
selector_down()
elif event is InputEventMouseButton:
if not event.pressed:
return
if check_event_pos(event.position, scroll):
if not in_focus:
in_focus = true
emit_signal('focus_entered')
elif not check_event_pos(event.position, scroll):
if in_focus:
in_focus = false
emit_signal('focus_exited')
##
func _init() -> void:
selector_color_proc = Callable(self, 'defaultselector_color_proc')
assert(selector_color_proc.is_valid())
selector_size_proc = Callable(self, 'default_selector_size_proc')
assert(selector_size_proc.is_valid())
scroll = self
##
func check_event_pos(event_pos: Vector2, obj: Control):
return \
(event_pos.x > obj.position.x) \
and (event_pos.x < (obj.position.x + obj.size.x)) \
and (event_pos.y > obj.position.y) \
and (event_pos.y < (obj.position.y + obj.size.y))
##
func defaultselector_color_proc(_i_row: int):
return selector_color
##
func setselector_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())
## [param i_row] - Индекс ряда
func default_selector_size_proc(i_row: int, rect: Array) -> void:
get_row_rect(i_row, rect)
rect[0] += position.x - node_gap.x / 2.0
rect[1] += position.y - node_gap.y / 2.0
rect[2] += node_gap.x
rect[3] += node_gap.y
##
func clear_rows_selected():
var rows_count: = get_rows_count()
for i_row in rows_count:
set_row_selected(i_row, false)
## [param i_row] - Индекс ряда.
func set_row_selected(i_row: int, selected: bool = true) -> void:
var key: = ROW_SELECTOR_KEY_FORMAT % [i_row, 0]
var node = nodes[NODE_KEY_FORMAT % [i_row, 0]]
if nodes.has(key) and (not selected):
var selector = nodes[key]
node.remove_child(selector)
selector.queue_free()
nodes.erase(key)
elif (not nodes.has(key)) and selected:
var selector = ScnSelector.instantiate()
var rect: = [0, 0, 0, 0]
selector_size_proc.call(i_row, rect)
selector.size.x = rect[2]
selector.size.y = rect[3]
selector.modulate = selector_color_proc.call(i_row)
selector.z_index = z_index + 1
nodes[key] = selector
node.add_child(selector)
## [param i_row] - Индекс ряда.
func is_row_selected(i_row: int) -> bool:
var key: = ROW_SELECTOR_KEY_FORMAT % [i_row, 0]
return nodes.has(key)
## [param i_row] - Индексы рядов.
func get_selected_rows(i_rows: Array) -> void:
for key in nodes.keys():
if SLCT_PREFIX in key:
var indexes = get_key_indexes(key)
i_rows.append(indexes[1])
## Возвращает количество рядов
func get_rows_count() -> int:
var nodes_count: int = 0
for key in nodes:
nodes_count += int(NODE_PREFIX in key)
return nodes_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 add_row(row_types: Array) -> void:
assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1))
if columns == 1:
set_columns(row_types.size())
else:
assert(columns == row_types.size(), 'ошибка: %s количество колонок установлено ранее - %d, но требуется %d' % [self, columns, row_types.size()])
var i_row: int = get_rows_count()
assert(i_row < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1))
for i_col in columns:
var node_type = row_types[i_col]
var node = node_type.instantiate()
assert(node.name == node.name, 'ошибка: экземпляр не имеет поля \"name\"')
var head = nodes[HEAD_KEY_FORMAT % [0, i_col]]
node.custom_minimum_size.x = head.custom_minimum_size.x
node.set_deferred('size.x', head.size.x)
var node_key: String = NODE_KEY_FORMAT % [i_row, i_col]
node.name = node_key
add_child(node)
nodes[node_key] = node
var conn = Callable(self, '_on_focus_entered').bind(node)
node.connect('focus_entered', conn)
set_node_meta(i_col, i_row, 'callable_focus_entered', conn)
##
func _on_focus_entered(node):
var indexes = get_key_indexes(node.name)
emit_signal('row_pressed', indexes[1])
##
func set_header(header_types: Array):
assert(header_types.size() < MAX_INDEX, 'ошибка: количество колонок в заголовке: %d больше допустимого: %d' % [header_types.size(), MAX_INDEX + 1])
if columns == 1:
set_columns(header_types.size())
else:
assert(columns == header_types.size(), 'ошибка: количество колонок в заголовке %d, но ранее установлено: %d' % [header_types.size(), columns])
var do_adj: = false
if not header:
header = GridContainer.new()
header.position = position
header.columns = header_types.size()
header.size.x = size.x
var parent = scroll.get_parent()
parent.add_child(header)
do_adj = true
else:
var childrens = header.get_children()
for node in childrens:
childrens.erase(node.name)
childrens.remove_child(node)
node.queue_free()
for i_col in columns:
var node = header_types[i_col].instantiate()
var key: String = HEAD_KEY_FORMAT % [0, i_col]
node.name = key
nodes[key] = node
header.add_child(node)
if do_adj:
var rect = [0, 0, 0, 0]
get_node_rect(0, 0, rect, HEAD_KEY_FORMAT)
position.y += rect[3]
##
func set_header_text(headers: Array):
assert(columns == headers.size())
set_row_text(0, headers, 0, HEAD_KEY_FORMAT)
##
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]
var prnt = selector.get_parent()
prnt.remove_child(selector)
selector.queue_free()
nodes.erase(sel_key)
for i_col in columns:
var node_key: String = NODE_KEY_FORMAT % [i_row, i_col]
var node = nodes[node_key]
var conn = get_node_meta(i_col, i_row, 'callable_focus_entered')
node.disconnect('focus_entered', conn)
if node.has_meta(META_NAME_CALLEBLE):
conn = node.get_meta(META_NAME_CALLEBLE)
node.disconnect('pressed', conn)
remove_child(node)
nodes.erase(node_key)
node.queue_free()
rehash()
##
func next_index(indexes: Array) -> void:
indexes[0].x += 1
if indexes[0].x == columns:
indexes[0].x = 0
indexes[0].y += 1
##
func swap_nodes(nodes_copy, node_key_format, i0, i1) -> void:
var key0 = node_key_format % [i0.y, i0.x]
var key1 = node_key_format % [i1.y, i1.x]
nodes_copy[key1] = nodes[key0]
nodes_copy[key1].name = key1
## Переназначает ключи в соответствии с размерами таблицы.
func rehash():
var i_node: = [Vector2i()]
var i_hdr: = [Vector2i()]
var i_slct: = [Vector2i()]
var nodes_copy = {}
for key in nodes:
var iv = get_key_indexes(key)
var i0 = Vector2(iv[0], iv[1])
if HEAD_PREFIX in key:
swap_nodes(nodes_copy, HEAD_KEY_FORMAT, i0, i_hdr[0])
next_index(i_hdr)
elif NODE_PREFIX in key:
swap_nodes(nodes_copy, NODE_KEY_FORMAT, i0, i_node[0])
next_index(i_node)
elif SLCT_PREFIX in key:
swap_nodes(nodes_copy, ROW_SELECTOR_KEY_FORMAT, i0, i_slct[0])
next_index(i_slct)
nodes.clear()
nodes.merge(nodes_copy)
## Удаляет все строки таблицы
func clear_rows() -> void:
var rows_count: int = get_rows_count()
for i_row in range(rows_count - 1, -1, -1):
remove_row(i_row)
## Настраевает обратные вызовы для колонок.
## [param dest_node] - Получатель события[br]
## [param event_name] - Имя события[br]
## [param callback] - Имя функции получателя события[br]
## [param clmns] - Индексы колонок[br]
## [param user_data] - Аргументы функции получателя события[br]
func connect_columns(dest_node: Node, event_name: String, callback: String, clmns: Array, user_data: Array) -> void:
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 = get_node2(i_col, i_row)
if node.has_meta(META_NAME_CALLEBLE):
var conn = node.get_meta(META_NAME_CALLEBLE)
node.disconnect(event_name, conn)
var conn = Callable(dest_node, callback).bind([node] + user_data)
assert (OK == node.connect(event_name, conn))
node.set_meta(META_NAME_CALLEBLE, conn)
##
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, key_format: String = NODE_KEY_FORMAT) -> void:
for i_col in columns:
var key: = get_node_key(key_format, i_col, i_row)
var node = nodes[key]
node.custom_minimum_size.x = sizes[i_col]
node.set_deferred('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[key].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
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
func set_node_frame(i_col: int, i_row: int, node_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(node_name)
sprite.frame = val
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
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
## [param i_col] - индекс колонки. [param i_row] - индекс ряда.
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)
if 'horizontal_alignment' in nodes[key]:
nodes[key].horizontal_alignment = val
elif 'alignment' in nodes[key]:
nodes[key].alignment = val
## [param i_row] - индекс ряда.
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]
## [param i_row] - индекс ряда.
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, key_format: String = NODE_KEY_FORMAT) -> void:
assert(i_begin >= 0)
assert((columns - i_begin) <= row.size())
for i_col in columns - i_begin:
var key: = get_node_key(key_format, i_col + i_begin, 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
## Возвращает массив ячеек из ряда [param i_row]
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
## Возвращает прямоугольник [param rect] занимаемый ячейкой в ряду [param i_row] и колонке [param i_col].
## [param key_format] - Формат ключа ячейки
func get_node_rect(i_col: int, i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void:
assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата')
var key: = get_node_key(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
## Возвращает прямоугольник [param rect] занимаемый рядом [param i_row]. [param key_format] - Формат ключа ячейки
func get_row_rect(i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void:
assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата')
var cell0 = nodes[get_node_key(key_format, 0, i_row)]
var cell1 = nodes[get_node_key(key_format, columns - 1, i_row)]
rect[0] = cell0.position.x + scroll_adj.x
rect[1] = cell0.position.y + scroll_adj.y
rect[2] = (cell1.position.x - cell0.position.x) + cell1.size.x
rect[3] = cell0.size.y