Added resource table plugin
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 8s
Create tag and build when new code gets to main / Export (push) Successful in 1m13s

This commit is contained in:
2026-01-31 10:23:20 +01:00
parent cd150a4513
commit 158e18f1fe
182 changed files with 9266 additions and 1 deletions

View File

@@ -0,0 +1,146 @@
@tool
extends Control
@onready var editor_view := $"../../../../.."
@onready var node_options := $"Class"
@onready var node_subclasses_check := $"Subclasses"
var found_builtins : Array[String] = []
var found_scripts : Array[Script] = []
var selected_builtin := &""
var selected_script : Script
var selected_is_valid := false
var include_subclasses := true
func _ready():
node_options.item_selected.connect(_on_item_selected)
node_subclasses_check.toggled.connect(_on_subclasses_toggled)
func fill(resources : Array):
node_options.clear()
found_scripts.clear()
found_builtins.clear()
var class_set := {}
for x in resources:
class_set[x.get_script()] = true
class_set[StringName(x.get_class())] = true
var current_s : Script = x.get_script()
while current_s != null:
current_s = current_s.get_base_script()
if class_set.has(current_s):
break
class_set[current_s] = true
var current_c : StringName = x.get_class()
while true:
if current_c == &"Resource":
break
current_c = ClassDB.get_parent_class(current_c)
if class_set.has(current_c):
break
class_set[current_c] = true
class_set.erase(null)
class_set.erase("Resource")
for k in class_set:
if k is StringName:
found_builtins.append(k)
if k is Script:
found_scripts.append(k)
# Add builtins, then script classes, in order.
node_options.add_item("<all>")
for x in found_builtins:
node_options.add_item(x)
if has_theme_icon(x, &"EditorIcons"):
node_options.set_item_icon(-1, get_theme_icon(x, &"EditorIcons"))
else:
node_options.set_item_icon(-1, get_theme_icon(&"Object", &"EditorIcons"))
for x in found_scripts:
node_options.add_item(x.resource_path.get_file().get_basename().to_pascal_case())
node_options.set_item_icon(-1, get_theme_icon(&"Script", &"EditorIcons"))
node_options.add_item("")
# Filter is disabled if the already selected class is not in the set.
if not class_set.has(selected_script) and not class_set.has(selected_builtin):
selected_is_valid = false
# When the list is cleared, text and icon are cleared too. Setting to -1 explicitly allows changing icon and label
node_options.selected = -1
if not selected_is_valid:
node_options.set_item_icon(-1, null)
elif selected_builtin == &"" or selected_builtin == &"Resource":
node_options.set_item_icon(-1, get_theme_icon("Script", "EditorIcons"))
else:
node_options.set_item_icon(-1, get_theme_icon(selected_builtin, "EditorIcons"))
show()
func clear():
selected_is_valid = false
func filter(resource : Resource) -> bool:
if not selected_is_valid:
return true
if resource.get_class() != selected_builtin:
if include_subclasses and selected_script == null:
var cur_class := StringName(resource.get_class())
while cur_class != &"Object":
cur_class = ClassDB.get_parent_class(cur_class)
if cur_class == selected_builtin:
return true
return false
if selected_script != null and resource.get_script() != selected_script:
if include_subclasses:
var cur_class : Script = resource.get_script()
while cur_class != null:
cur_class = cur_class.get_base_script()
if cur_class == selected_script:
return true
return false
return true
func _on_item_selected(index : int):
if index == 0:
selected_builtin = &""
selected_script = null
selected_is_valid = false
elif index <= found_builtins.size():
selected_builtin = found_builtins[index - 1]
selected_script = null
selected_is_valid = true
elif index <= found_builtins.size() + found_scripts.size():
selected_script = found_scripts[index - found_builtins.size() - 1]
selected_builtin = selected_script.get_instance_base_type()
selected_is_valid = true
node_options.tooltip_text = "Selected: %s" % node_options.get_item_text(index)
editor_view.refresh()
func _on_subclasses_toggled(state : bool):
include_subclasses = state
editor_view.refresh()

View File

@@ -0,0 +1 @@
uid://dbnhniu1anvc7

View File

@@ -0,0 +1,182 @@
@tool
extends Control
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
@export var table_header_scene : PackedScene
@onready var editor_view : Control = $"../../../.."
@onready var hide_columns_button : MenuButton = $"../../MenuStrip/VisibleCols"
@onready var grid : Container = $"../../../MarginContainer/FooterContentSplit/Panel/Scroll/MarginContainer/TableGrid"
var hidden_columns := {}:
get:
var result := {}
for k_path in column_properties:
var result_one_path := {}
result[k_path] = result_one_path
for k_column in column_properties[k_path]:
for k_property in column_properties[k_path][k_column]:
if k_property == &"visibility" && [k_property]:
result[k_path][k_column] = true
return result
var column_properties := {}
var columns := []:
set(v):
columns = v
for x in get_children():
remove_child(x)
x.queue_free()
var new_node : Control
for x in v:
new_node = table_header_scene.instantiate()
new_node.manager = self
add_child(new_node)
new_node.set_label(x)
new_node.get_node("Button").pressed.connect(editor_view._set_sorting.bind(x))
_update_column_sizes()
func _ready():
hide_columns_button\
.get_popup()\
.id_pressed\
.connect(_on_visible_cols_id_pressed)
$"../../../MarginContainer/FooterContentSplit/Panel/Scroll"\
.get_h_scroll_bar()\
.value_changed\
.connect(_on_h_scroll_changed)
func update():
_update_hidden_columns()
_update_column_sizes()
func hide_column(column_index : int):
set_column_property(column_index, &"visibility", 0)
editor_view.save_data()
update()
func set_column_property(column_index : int, property_key : StringName, property_value):
var dict := column_properties
if !dict.has(editor_view.current_path):
dict[editor_view.current_path] = {}
dict = dict[editor_view.current_path]
if !dict.has(columns[column_index]):
dict[columns[column_index]] = {}
dict = dict[columns[column_index]]
dict[property_key] = property_value
func get_column_property(column_index : int, property_key : StringName, property_default = null):
var dict := column_properties
if !dict.has(editor_view.current_path):
return property_default
dict = dict[editor_view.current_path]
if !dict.has(columns[column_index]):
return property_default
dict = dict[columns[column_index]]
return dict.get(property_key, property_default)
func select_column(column_index : int):
editor_view.select_column(column_index)
func _update_column_sizes():
if grid.get_child_count() == 0:
return
await get_tree().process_frame
var column_headers := get_children()
if grid.get_child_count() < column_headers.size(): return
if column_headers.size() != columns.size():
editor_view.refresh()
return
var clip_text : bool = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "clip_headers")
var visible_column_minsizes : Array[float] = []
for i in column_headers.size():
var header = column_headers[i]
if header.visible:
header.get_child(0).clip_text = clip_text
visible_column_minsizes.append(header.get_combined_minimum_size().x)
grid.visible_column_minsizes = visible_column_minsizes
await get_tree().process_frame
# Abort if the node has been deleted since.
if !is_instance_valid(column_headers[0]):
return
get_parent().custom_minimum_size.y = column_headers[0].get_combined_minimum_size().y
var column_positions : Array = grid.visible_column_positions
var i := 0
for x in column_headers:
if !x.visible:
continue
var pos : float = column_positions[i]
x.position.x = pos
x.size.x = column_positions[i + 1] - pos
i += 1
func _update_hidden_columns():
var current_path : String = editor_view.current_path
var rows_shown : int = editor_view.last_row - editor_view.first_row
if !column_properties.has(current_path):
column_properties[current_path] = {
"resource_local_to_scene" : { &"visibility" : 0 },
"resource_name" : { &"visibility" : 0 },
"metadata/_custom_type_script" : { &"visibility" : 0 },
}
editor_view.save_data()
var visible_column_count := 0
for i in columns.size():
var column_visible : bool = get_column_property(i, &"visibility", 1) != 0
get_child(i).visible = column_visible
for j in rows_shown:
grid.get_child(j * columns.size() + i).visible = column_visible
if column_visible:
visible_column_count += 1
func _on_h_scroll_changed(value):
position.x = -value
func _on_visible_cols_about_to_popup():
var popup := hide_columns_button.get_popup()
popup.clear()
popup.hide_on_checkable_item_selection = false
for i in columns.size():
popup.add_check_item(columns[i].capitalize(), i)
popup.set_item_checked(i, get_column_property(i, &"visibility", 1) != 0)
func _on_visible_cols_id_pressed(id : int):
var popup := hide_columns_button.get_popup()
if popup.is_item_checked(id):
popup.set_item_checked(id, false)
set_column_property(id, &"visibility", 0)
else:
popup.set_item_checked(id, true)
set_column_property(id, &"visibility", 1)
editor_view.save_data()
update()

View File

@@ -0,0 +1 @@
uid://ot866hxu2g4m

View File

@@ -0,0 +1,73 @@
@tool
extends Container
var visible_column_minsizes : Array = []:
set(v):
visible_column_minsizes = v
queue_sort()
var visible_column_positions : Array[float] = []
var _cached_minimum_size := Vector2.ZERO
func _notification(what : int) -> void:
if what == NOTIFICATION_SORT_CHILDREN:
var visible_children : Array[Control] = []
for x in get_children():
if x is Control and x.visible:
visible_children.append(x)
sort_children(visible_children)
func _get_minimum_size() -> Vector2:
return _cached_minimum_size
func get_visible_column_position(index : int):
pass
func sort_children(children : Array[Control]) -> void:
var column_count := visible_column_minsizes.size()
if column_count == 0:
return
var column_minsizes : Array[float] = []
var row_minsizes : Array[float] = []
column_minsizes.resize(column_count)
row_minsizes.resize(children.size() / column_count + 1)
for i in visible_column_minsizes.size():
column_minsizes[i] = visible_column_minsizes[i]
var current_cell := Vector2i.ZERO
for x in children:
var minsize := x.get_combined_minimum_size()
column_minsizes[current_cell.x] = maxf(column_minsizes[current_cell.x], minsize.x)
row_minsizes[current_cell.y] = maxf(row_minsizes[current_cell.y], minsize.y)
current_cell.x += 1
if current_cell.x == column_count:
current_cell.x = 0
current_cell.y += 1
var current_pos := Vector2.ZERO
current_cell = Vector2i.ZERO
for x in children:
var cur_size := Vector2(column_minsizes[current_cell.x], row_minsizes[current_cell.y])
fit_child_in_rect(x, Rect2(current_pos, cur_size))
current_pos.x += cur_size.x
current_cell.x += 1
if current_cell.x == column_count:
current_cell.x = 0
current_cell.y += 1
current_pos.x = 0.0
current_pos.y += cur_size.y
_cached_minimum_size = Vector2.ZERO
visible_column_positions.resize(column_minsizes.size() + 1)
for i in column_minsizes.size():
_cached_minimum_size.x += column_minsizes[i]
visible_column_positions[i + 1] = _cached_minimum_size.x
for x in row_minsizes:
_cached_minimum_size.y += x

View File

@@ -0,0 +1 @@
uid://dky1oatlfcbuf

View File

@@ -0,0 +1,173 @@
@tool
extends Control
@export var editor_view_path : NodePath
@export_enum("Filter", "Process", "Sort") var mode := 0
@export var title := ""
@export var default_text := "":
set(v):
default_text = v
if _textfield == null:
await ready
_textfield.text = v
@export_multiline var default_text_ml := "":
set(v):
default_text_ml = v
if _textfield_ml == null:
await ready
_textfield_ml.text = v
@export var function_save_key := ""
var _textfield : LineEdit
var _textfield_ml : TextEdit
var _togglable_popup : PopupPanel
var _saved_function_index_label : Label
var _saved_functions : Array = []
var _saved_function_selected := -1
func load_saved_functions(func_dict : Dictionary):
if !func_dict.has(function_save_key):
func_dict[function_save_key] = [default_text_ml]
_saved_functions = func_dict[function_save_key]
_on_saved_function_selected(0)
func _ready():
var toggle_button := Button.new()
var popup_box := VBoxContainer.new()
var popup_buttons_box := HBoxContainer.new()
var title_label := Label.new()
var submit_button := Button.new()
var move_label := Label.new()
var move_button_l := Button.new()
var move_button_r := Button.new()
_textfield = LineEdit.new()
_togglable_popup = PopupPanel.new()
_textfield_ml = TextEdit.new()
_saved_function_index_label = Label.new()
add_child(_textfield)
add_child(toggle_button)
_textfield.add_child(_togglable_popup)
_togglable_popup.add_child(popup_box)
popup_box.add_child(title_label)
popup_box.add_child(_textfield_ml)
popup_box.add_child(popup_buttons_box)
popup_buttons_box.add_child(submit_button)
popup_buttons_box.add_child(move_label)
popup_buttons_box.add_child(move_button_l)
popup_buttons_box.add_child(_saved_function_index_label)
popup_buttons_box.add_child(move_button_r)
title_label.text = title
toggle_button.icon = get_theme_icon("Collapse", "EditorIcons")
toggle_button.pressed.connect(_on_expand_pressed)
_textfield.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_textfield.text_submitted.connect(_on_text_submitted.unbind(1))
_textfield_ml.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_textfield_ml.size_flags_vertical = Control.SIZE_EXPAND_FILL
submit_button.text = "Run multiline!"
submit_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
submit_button.pressed.connect(_on_text_submitted)
move_label.text = "Choose saved:"
move_button_l.icon = get_theme_icon("PagePrevious", "EditorIcons")
move_button_l.pressed.connect(_on_saved_function_bumped.bind(-1))
_on_saved_function_selected(0)
move_button_r.icon = get_theme_icon("PageNext", "EditorIcons")
move_button_r.pressed.connect(_on_saved_function_bumped.bind(+1))
func _on_expand_pressed():
_togglable_popup.popup(Rect2i(_textfield.get_screen_position(), Vector2(size.x, 256.0)))
func _on_text_submitted():
[_table_filter, _table_process][mode].call()
_saved_functions[_saved_function_selected] = _textfield_ml.text
get_node(editor_view_path).save_data.call_deferred()
func _get_script_source_code(first_line : String):
var new_text := ""
if !_togglable_popup.visible:
new_text = _textfield.text
if new_text == "":
new_text = default_text
return first_line + "\treturn " + new_text
else:
new_text = _textfield_ml.text
if new_text == "":
new_text = default_text_ml
var text_split := new_text.split("\n")
for i in text_split.size():
text_split[i] = "\t" + text_split[i]
return first_line + "\n".join(text_split)
func _table_filter():
var new_script := GDScript.new()
new_script.source_code = _get_script_source_code("static func can_show(res : Resource, index : int) -> bool:\n")
new_script.reload()
var editor_view := get_node(editor_view_path)
editor_view.search_cond = new_script.can_show
editor_view.refresh()
func _table_process():
var new_script := GDScript.new()
new_script.source_code = _get_script_source_code("static func get_result(value : Variant, res : Resource, all_res : Array[Resource], row_index : int) -> Variant:\n")
new_script.reload()
var editor_view := get_node(editor_view_path)
var new_script_instance := new_script.new()
var values : Array = editor_view.get_edited_cells_values()
var edited_rows : Array[int] = editor_view._selection.get_edited_rows()
var edited_resources := edited_rows.map(func(x): return editor_view.rows[x])
for i in values.size():
values[i] = new_script_instance.get_result(values[i], editor_view.rows[edited_rows[i]], edited_resources, i)
editor_view.set_edited_cells_values(values)
func _on_saved_function_selected(new_index : int):
if new_index < 0:
new_index = 0
if _saved_function_selected == _saved_functions.size() - 1 and _textfield_ml.text == default_text_ml:
_saved_functions.resize(_saved_functions.size() - 1)
elif _saved_function_selected >= 0:
_saved_functions[_saved_function_selected] = _textfield_ml.text
_saved_function_selected = new_index
if new_index >= _saved_functions.size():
_saved_functions.resize(new_index + 1)
for i in _saved_functions.size():
if _saved_functions[i] == null:
_saved_functions[i] = default_text_ml
_textfield_ml.text = _saved_functions[new_index]
_saved_function_index_label.text = "%d/%d" % [new_index + 1, _saved_functions.size()]
get_node(editor_view_path).save_data.call_deferred()
func _on_saved_function_bumped(increment : int):
_on_saved_function_selected(_saved_function_selected + increment)

View File

@@ -0,0 +1 @@
uid://desytritrg7nu

View File

@@ -0,0 +1,60 @@
@tool
extends ColorRect
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
@onready var editor_view : Control = $"../../../../../.."
@onready var grid_scroll : ScrollContainer = $"../../Scroll"
@onready var grid : Container = $"../../Scroll/MarginContainer/TableGrid"
var children : Array[Control] = []
var children_copy_cells : Array[Control] = []
func _ready() -> void:
grid_scroll.get_h_scroll_bar().value_changed.connect(_on_scroll_updated, CONNECT_DEFERRED)
grid_scroll.get_v_scroll_bar().value_changed.connect(_on_scroll_updated, CONNECT_DEFERRED)
func _on_grid_updated() -> void:
if editor_view.rows.size() == 0:
hide()
return
visible = ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "freeze_first_column")
for x in get_children():
x.queue_free()
children.clear()
children_copy_cells.clear()
size.x = 0.0
await get_tree().process_frame
var first_visible_column := 0
for i in editor_view.columns.size():
if grid.get_child(i).visible:
first_visible_column = i
break
var total_column_count : int = editor_view.columns.size()
children.resize(grid.get_child_count() / total_column_count)
children_copy_cells.resize(children.size())
for i in children.size():
children_copy_cells[i] = grid.get_child(total_column_count * i + first_visible_column)
children[i] = children_copy_cells[i].duplicate()
children[i].mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(children[i])
size.x = maxf(size.x, children_copy_cells[i].size.x)
size.y = grid.size.y
color = get_theme_color(&"background", &"Editor")
color.a *= 0.9
_on_scroll_updated(0.0)
func _on_scroll_updated(_new_value : float):
position = Vector2(0.0, -grid_scroll.scroll_vertical)
for i in children.size():
children[i].size = children_copy_cells[i].size
children[i].position = children_copy_cells[i].position

View File

@@ -0,0 +1 @@
uid://dwf6rnqofdagb

View File

@@ -0,0 +1,167 @@
@tool
extends Node
const TablesPluginEditorViewClass := preload("res://addons/resources_spreadsheet_view/editor_view.gd")
const TablesPluginSelectionManagerClass := preload("res://addons/resources_spreadsheet_view/main_screen/selection_manager.gd")
const TextEditingUtilsClass := preload("res://addons/resources_spreadsheet_view/text_editing_utils.gd")
@onready var editor_view : TablesPluginEditorViewClass = get_parent()
@onready var selection : TablesPluginSelectionManagerClass = get_node("../SelectionManager")
func _on_cell_gui_input(event : InputEvent, cell_node : Control):
var cell := selection.get_cell_node_position(cell_node)
if event is InputEventMouseButton:
editor_view.grab_focus()
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
if !cell in selection.edited_cells:
selection.deselect_all_cells()
selection.select_cell(cell)
selection.rightclick_cells()
if event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
if event.is_command_or_control_pressed():
if cell in selection.edited_cells:
selection.deselect_cell(cell)
else:
selection.select_cell(cell)
elif Input.is_key_pressed(KEY_SHIFT):
selection.select_cells_to(cell)
else:
selection.deselect_all_cells()
selection.select_cell(cell)
func _gui_input(event : InputEvent):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed():
selection.rightclick_cells()
if event.button_index == MOUSE_BUTTON_LEFT:
editor_view.grab_focus()
if !event.pressed:
selection.deselect_all_cells()
func _input(event : InputEvent):
if !event is InputEventKey or !event.pressed:
return
if !editor_view.has_focus() or selection.edited_cells.size() == 0:
return
if event.keycode == KEY_CTRL or event.keycode == KEY_SHIFT or event.keycode == KEY_META:
# Modifier keys do not get processed.
return
# Ctrl + Z (before, and instead of, committing the action!)
if event.is_command_or_control_pressed():
if event.keycode == KEY_Z or event.keycode == KEY_Y:
return
_key_specific_action(event)
editor_view.grab_focus()
editor_view.editor_interface.get_resource_filesystem().scan()
func _key_specific_action(event : InputEvent):
var column := selection.get_cell_column(selection.edited_cells[0])
var ctrl_pressed : bool = event.is_command_or_control_pressed()
# BETWEEN-CELL NAVIGATION
var grid_move_offset := (10 if ctrl_pressed else 1)
if event.keycode == KEY_UP:
_move_selection_on_grid(0, -grid_move_offset)
elif event.keycode == KEY_DOWN:
_move_selection_on_grid(0, +grid_move_offset)
elif Input.is_key_pressed(KEY_SHIFT) and event.keycode == KEY_TAB:
_move_selection_on_grid(-grid_move_offset, 0)
elif event.keycode == KEY_TAB:
_move_selection_on_grid(+grid_move_offset, 0)
elif ctrl_pressed and event.keycode == KEY_C:
TextEditingUtilsClass.multi_copy(selection.edited_cells_text)
get_viewport().set_input_as_handled()
# Ctrl + V
elif ctrl_pressed and event.keycode == KEY_V and editor_view.columns[column] != "resource_path":
selection.clipboard_paste()
get_viewport().set_input_as_handled()
# TEXT CARET MOVEMENT
var caret_move_offset := TextEditingUtilsClass.get_caret_movement_from_key(event.keycode)
if TextEditingUtilsClass.multi_move_caret(caret_move_offset, selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed):
selection.queue_redraw()
return
# The following actions do not work on non-editable cells.
if !selection.column_editors[column].is_text() or editor_view.columns[column] == "resource_path":
return
# ERASING
elif event.keycode == KEY_BACKSPACE:
editor_view.set_edited_cells_values_text(TextEditingUtilsClass.multi_erase_left(
selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed
))
elif event.keycode == KEY_DELETE:
editor_view.set_edited_cells_values_text(TextEditingUtilsClass.multi_erase_right(
selection.edited_cells_text, selection.edit_cursor_positions, ctrl_pressed
))
get_viewport().set_input_as_handled()
# And finally, text typing.
elif event.keycode == KEY_ENTER:
editor_view.set_edited_cells_values_text(TextEditingUtilsClass.multi_input(
"\n", selection.edited_cells_text, selection.edit_cursor_positions
))
elif event.unicode != 0 and event.unicode != 127:
editor_view.set_edited_cells_values_text(TextEditingUtilsClass.multi_input(
char(event.unicode), selection.edited_cells_text, selection.edit_cursor_positions
))
selection.queue_redraw()
func _move_selection_on_grid(move_h : int, move_v : int):
var selected_cells := selection.edited_cells.duplicate()
var num_columns := editor_view.columns.size()
var num_rows := editor_view.rows.size()
var new_child_pos := Vector2i(0, 0)
for i in selected_cells.size():
new_child_pos = selected_cells[i]
for move_count in 1000:
if move_v != 0 and (new_child_pos.y + move_v >= num_rows or new_child_pos.y + move_v < 0):
break
new_child_pos.x += move_h
new_child_pos.y += move_v
if new_child_pos.x < 0:
new_child_pos.x = num_columns - 1
if new_child_pos.y > 0:
new_child_pos.y -= 1
if new_child_pos.x >= num_columns:
new_child_pos.x = 0
if new_child_pos.y < num_rows - 1:
new_child_pos.y += 1
if selection.get_cell_node_from_position(new_child_pos) == null:
break
if selection.get_cell_node_from_position(new_child_pos).is_visible_in_tree():
break
selected_cells[i] = new_child_pos
editor_view.grab_focus()
selection.deselect_all_cells()
selection.select_cells(selected_cells)

View File

@@ -0,0 +1 @@
uid://brea0xl3xhde0

View File

@@ -0,0 +1,54 @@
@tool
extends OptionButton
@onready var editor_view := $"../../../../.."
var recent_paths := []
func _ready():
item_selected.connect(_on_item_selected)
func load_paths(paths):
clear()
for x in paths:
add_path_to_recent(x, true)
selected = recent_paths.size() - 1
func add_path_to_recent(path : String, is_loading : bool = false):
if path in recent_paths: return
var idx_in_array := recent_paths.find(path)
if idx_in_array != -1:
remove_item(idx_in_array)
recent_paths.remove_at(idx_in_array)
recent_paths.append(path)
add_item(path)
select(get_item_count() - 1)
if !is_loading:
editor_view.save_data()
func remove_selected_path_from_recent():
if get_item_count() == 0:
return
var idx_in_array := selected
recent_paths.remove_at(idx_in_array)
remove_item(idx_in_array)
if get_item_count() != 0:
select(0)
editor_view.display_folder(recent_paths[0])
editor_view.save_data()
func _on_item_selected(index : int):
editor_view.current_path = recent_paths[index]
editor_view.node_folder_path.text = recent_paths[index]
editor_view.refresh()

View File

@@ -0,0 +1 @@
uid://1f6sthbkwbwq

View File

@@ -0,0 +1,191 @@
@tool
extends MarginContainer
enum {
EDITBOX_DUPLICATE = 1,
EDITBOX_RENAME,
EDITBOX_DELETE,
}
const TextEditingUtilsClass := preload("res://addons/resources_spreadsheet_view/text_editing_utils.gd")
const TablesPluginSettingsClass := preload("res://addons/resources_spreadsheet_view/settings_grid.gd")
@onready var editor_view := $"../.."
@onready var selection := $"../../SelectionManager"
@onready var editbox_node := $"Control/ColorRect/Popup"
@onready var editbox_label : Label = editbox_node.get_node("Panel/VBoxContainer/Label")
@onready var editbox_input : LineEdit = editbox_node.get_node("Panel/VBoxContainer/LineEdit")
var cell : Control
var editbox_action : int
func _ready():
editbox_input.get_node("../..").add_theme_stylebox_override(
"panel",
get_theme_stylebox(&"Content", &"EditorStyles")
)
editbox_input.text_submitted.connect(func(_new_text): _on_editbox_accepted())
close()
func _on_grid_cells_rightclicked(cells):
open(cells)
func _on_grid_cells_selected(cells):
open(cells, true, true)
func open(cells : Array, pin_to_cell : bool = false, from_leftclick : bool = false):
if cells.size() == 0 or (from_leftclick and !ProjectSettings.get_setting(TablesPluginSettingsClass.PREFIX + "context_menu_on_leftclick")):
hide()
cell = null
return
if pin_to_cell:
cell = selection.get_cell_node_from_position(cells[-1])
set_deferred(&"global_position", Vector2(
cell.global_position.x + cell.size.x,
cell.global_position.y
))
else:
cell = null
set_deferred(&"global_position", get_global_mouse_position() + Vector2.ONE)
show()
size = Vector2.ZERO
top_level = true
$"Control2/Label".text = str(cells.size()) + (" Cells" if cells.size() % 10 != 1 else " Cell")
$"GridContainer/Rename".visible = editor_view.has_row_names()
$"GridContainer/SoloOpen".visible = editor_view.column_can_solo_open(editor_view.get_selected_column())
func close():
pass
func _input(event : InputEvent):
if !editor_view.is_visible_in_tree():
close()
return
if event is InputEventMouseButton and event.is_pressed():
close()
return
if event is InputEventKey:
if event.is_pressed() and event.is_command_or_control_pressed():
global_position = get_global_mouse_position() + Vector2.ONE
if cell != null:
global_position = Vector2(
cell.global_position.x + cell.size.x,
cell.global_position.y
)
# Dupe
if event.keycode == KEY_D:
_on_Duplicate_pressed()
return
# Rename
if event.keycode == KEY_R:
_on_Rename_pressed()
return
func _on_Duplicate_pressed():
_show_editbox(EDITBOX_DUPLICATE)
func _on_CbCopy_pressed():
TextEditingUtilsClass.multi_copy(selection.edited_cells_text)
func _on_CbPaste_pressed():
selection.clipboard_paste()
func _on_Rename_pressed():
_show_editbox(EDITBOX_RENAME)
func _on_Delete_pressed():
_show_editbox(EDITBOX_DELETE)
func _on_SoloOpen_pressed():
var resources_to_open_unique := {}
for x in editor_view.get_edited_cells_values():
if x is Array:
for y in x:
resources_to_open_unique[y] = true
if x is Resource:
resources_to_open_unique[x] = true
if resources_to_open_unique.size() > 0:
editor_view.display_resources(resources_to_open_unique.keys())
func _show_editbox(action):
editbox_action = action
match action:
EDITBOX_DUPLICATE:
if !editor_view.has_row_names():
_on_editbox_accepted()
return
if selection.edited_cells.size() == 1:
editbox_label.text = "Input new row's name..."
editbox_input.text = editor_view.get_last_selected_row()\
.resource_path.get_file().get_basename()
else:
editbox_label.text = "Input suffix to append to names..."
editbox_input.text = ""
EDITBOX_RENAME:
editbox_label.text = "Input new name for row..."
editbox_input.text = editor_view.get_last_selected_row()\
.resource_path.get_file().get_basename()
EDITBOX_DELETE:
editbox_label.text = "Really delete selected rows? (Irreversible!!!)"
editbox_input.text = editor_view.get_last_selected_row()\
.resource_path.get_file().get_basename()
show()
editbox_input.grab_focus()
editbox_input.caret_column = 999999999
editbox_node.size = Vector2.ZERO
editbox_node.show()
$"Control/ColorRect".show()
$"Control/ColorRect".top_level = true
$"Control/ColorRect".size = get_viewport_rect().size * 4.0
editbox_node.global_position = (
global_position
+ size * 0.5
- editbox_node.get_child(0).size * 0.5
)
func _on_editbox_closed():
editbox_node.hide()
$"Control/ColorRect".hide()
func _on_editbox_accepted():
match(editbox_action):
EDITBOX_DUPLICATE:
editor_view.duplicate_selected_rows(editbox_input.text)
EDITBOX_RENAME:
editor_view.rename_row(editor_view.get_last_selected_row(), editbox_input.text)
EDITBOX_DELETE:
editor_view.delete_selected_rows()
_on_editbox_closed()

View File

@@ -0,0 +1 @@
uid://dhsfesyigu88y

View File

@@ -0,0 +1,191 @@
[gd_scene load_steps=7 format=3 uid="uid://b51hnttsie7k5"]
[ext_resource type="Script" uid="uid://dyxdmrhopt5i0" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="1"]
[ext_resource type="Script" uid="uid://dhsfesyigu88y" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.gd" id="1_qv6ov"]
[ext_resource type="Script" uid="uid://duxju2vxsk50o" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_a4ihj"]
[sub_resource type="Image" id="Image_qja2v"]
data = {
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
"format": "LumAlpha8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="2"]
image = SubResource("Image_qja2v")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_r2l2b"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(1, 0.365, 0.365, 1)
draw_center = false
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
corner_detail = 1
[node name="SelectionActions" type="MarginContainer"]
offset_right = 80.0
offset_bottom = 52.0
size_flags_horizontal = 9
mouse_filter = 2
script = ExtResource("1_qv6ov")
[node name="Control2" type="Panel" parent="."]
layout_mode = 2
mouse_filter = 2
[node name="ColorRect" type="ColorRect" parent="Control2"]
show_behind_parent = true
layout_mode = 0
mouse_filter = 2
[node name="ColorRect2" type="ColorRect" parent="Control2"]
modulate = Color(0, 0, 0, 1)
show_behind_parent = true
layout_mode = 1
anchors_preset = 9
anchor_bottom = 1.0
offset_left = -2.0
grow_vertical = 2
mouse_filter = 2
script = ExtResource("2_a4ihj")
[node name="Label" type="Label" parent="Control2"]
layout_mode = 0
offset_top = -26.0
offset_right = 57.0
text = "Actions"
vertical_alignment = 2
[node name="ColorRect3" type="Panel" parent="Control2/Label"]
show_behind_parent = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -2.0
offset_top = 2.0
offset_right = 2.0
grow_horizontal = 2
grow_vertical = 2
mouse_filter = 2
[node name="GridContainer" type="GridContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 9
size_flags_vertical = 9
mouse_filter = 2
columns = 3
[node name="Duplicate" type="Button" parent="GridContainer"]
layout_mode = 2
tooltip_text = "Duplicate Selected Rows (Ctrl+D)"
mouse_filter = 1
icon = SubResource("2")
script = ExtResource("1")
icon_name = "Duplicate"
[node name="CbCopy" type="Button" parent="GridContainer"]
layout_mode = 2
tooltip_text = "Copy to Clipboard (Ctrl+C)"
mouse_filter = 1
icon = SubResource("2")
script = ExtResource("1")
icon_name = "ActionCopy"
[node name="CbPaste" type="Button" parent="GridContainer"]
layout_mode = 2
tooltip_text = "Paste Clipboard (Ctrl+V)"
mouse_filter = 1
icon = SubResource("2")
script = ExtResource("1")
icon_name = "ActionPaste"
[node name="Rename" type="Button" parent="GridContainer"]
layout_mode = 2
tooltip_text = "Rename Selected Rows (Ctrl+R)"
mouse_filter = 1
icon = SubResource("2")
script = ExtResource("1")
icon_name = "Edit"
[node name="Delete" type="Button" parent="GridContainer"]
layout_mode = 2
tooltip_text = "DELETE Selected Rows"
mouse_filter = 1
icon = SubResource("2")
script = ExtResource("1")
icon_name = "Remove"
[node name="SoloOpen" type="Button" parent="GridContainer"]
layout_mode = 2
tooltip_text = "Open selected cells as new table"
mouse_filter = 1
icon = SubResource("2")
script = ExtResource("1")
icon_name = "Object"
[node name="Control" type="Control" parent="."]
layout_mode = 2
mouse_filter = 2
[node name="ColorRect" type="ColorRect" parent="Control"]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -80.0
offset_bottom = -52.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0, 0, 0, 0.498039)
[node name="Popup" type="MarginContainer" parent="Control/ColorRect"]
layout_mode = 0
offset_top = 100.0
offset_right = 140.0
offset_bottom = 196.0
[node name="Panel" type="PanelContainer" parent="Control/ColorRect/Popup"]
layout_mode = 2
mouse_filter = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_r2l2b")
[node name="VBoxContainer" type="VBoxContainer" parent="Control/ColorRect/Popup/Panel"]
layout_mode = 2
mouse_filter = 2
[node name="Label" type="Label" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
layout_mode = 2
text = "Input new name..."
[node name="LineEdit" type="LineEdit" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Button" type="Button" parent="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "OK"
[node name="Button2" type="Button" parent="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Cancel"
[connection signal="pressed" from="GridContainer/Duplicate" to="." method="_on_Duplicate_pressed"]
[connection signal="pressed" from="GridContainer/CbCopy" to="." method="_on_CbCopy_pressed"]
[connection signal="pressed" from="GridContainer/CbPaste" to="." method="_on_CbPaste_pressed"]
[connection signal="pressed" from="GridContainer/Rename" to="." method="_on_Rename_pressed"]
[connection signal="pressed" from="GridContainer/Delete" to="." method="_on_Delete_pressed"]
[connection signal="pressed" from="GridContainer/SoloOpen" to="." method="_on_SoloOpen_pressed"]
[connection signal="pressed" from="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer/Button" to="." method="_on_editbox_accepted"]
[connection signal="pressed" from="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer/Button2" to="." method="_on_editbox_closed"]

View File

@@ -0,0 +1,321 @@
@tool
extends Control
signal cells_selected(cells_positions)
signal cells_rightclicked(cells_positions)
const EditorViewClass := preload("res://addons/resources_spreadsheet_view/editor_view.gd")
const TextEditingUtilsClass := preload("res://addons/resources_spreadsheet_view/text_editing_utils.gd")
@export var cell_editor_classes : Array[Script] = []
@onready var node_property_editors : VBoxContainer = $"../HeaderContentSplit/MarginContainer/FooterContentSplit/Footer/PropertyEditors"
@onready var scrollbar : ScrollContainer = $"../HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Scroll"
@onready var editor_view : EditorViewClass = get_parent()
var edited_cells : Array = []
var edited_cells_text : Array = []
var edit_cursor_positions : Array[int] = []
var all_cell_editors : Array = []
var column_editors : Array[Object] = []
var inspector_resource : Resource
func _ready():
# Load cell editors and instantiate them
for x in cell_editor_classes:
all_cell_editors.append(x.new())
all_cell_editors[all_cell_editors.size() - 1].hint_strings_array = editor_view.column_hint_strings
get_parent()\
.editor_interface\
.get_inspector()\
.property_edited\
.connect(_on_inspector_property_edited)
scrollbar.get_h_scroll_bar().value_changed.connect(queue_redraw.unbind(1), CONNECT_DEFERRED)
scrollbar.get_v_scroll_bar().value_changed.connect(queue_redraw.unbind(1), CONNECT_DEFERRED)
if ProjectSettings.get_setting(editor_view.TablesPluginSettingsClass.PREFIX + "fold_docks", false):
for x in node_property_editors.get_children():
x.resize_set_hidden(true)
func _draw():
if edited_cells.size() == 0 or edit_cursor_positions.size() != edited_cells.size() or !column_editors[edited_cells[0].x].is_text():
return
var font := get_theme_font(&"font", &"Label")
var font_size := get_theme_font_size(&"font", &"Label")
var caret_color := get_theme_color(&"caret_color", &"LineEdit")
var label_padding_left := 2.0
var newline_char := 10
for i in edited_cells.size():
var cell : Control = get_cell_node_from_position(edited_cells[i])
var caret_rect := Rect2()
if cell.has_method(&"get_character_bounds"):
if edited_cells_text[i].length() == edit_cursor_positions[i]:
caret_rect = cell.get_character_bounds(edit_cursor_positions[i] - 1)
caret_rect.position.x += caret_rect.size.x
else:
caret_rect = cell.get_character_bounds(edit_cursor_positions[i])
caret_rect.size.x = 1.0
else:
caret_rect = TextEditingUtilsClass.get_caret_rect(edited_cells_text[i], edit_cursor_positions[i], font, font_size, label_padding_left, 1.0)
caret_rect.position += cell.global_position - global_position
draw_rect(caret_rect, caret_color)
func initialize_editors(column_values, column_types, column_hints):
_set_visible_selected(false)
column_editors.clear()
for i in column_values.size():
for x in all_cell_editors:
if x.can_edit_value(column_values[i], column_types[i], column_hints[i], i):
column_editors.append(x)
break
func deselect_all_cells():
_set_visible_selected(false)
edited_cells.clear()
edited_cells_text.clear()
edit_cursor_positions.clear()
_selection_changed()
func deselect_cell(cell : Vector2i):
var idx := edited_cells.find(cell)
if idx == -1: return
edited_cells.remove_at(idx)
if edited_cells_text.size() != 0:
edited_cells_text.remove_at(idx)
edit_cursor_positions.remove_at(idx)
var cell_node := get_cell_node_from_position(cell)
if cell_node != null:
column_editors[get_cell_column(cell)].set_selected(cell_node, false)
_selection_changed()
func select_cell(cell : Vector2i):
var column_index := get_cell_column(cell)
if edited_cells.size() == 0 or edited_cells[0].x == cell.x:
_add_cell_to_selection(cell)
_try_open_docks(cell)
inspector_resource = editor_view.rows[get_cell_row(cell)]
editor_view.editor_plugin.get_editor_interface().edit_resource(inspector_resource)
_selection_changed()
func select_cells(cells : Array):
var last_selectible := Vector2i(-1, -1)
var started_empty := edited_cells.size() == 0
for x in cells:
if started_empty or edited_cells[0].x != x.x:
_add_cell_to_selection(x)
if get_cell_node_from_position(x) != null:
last_selectible = x
if last_selectible != Vector2i(-1, -1):
select_cell(last_selectible)
func select_cells_to(cell : Vector2i):
var column_index := get_cell_column(cell)
if edited_cells.size() == 0 or column_index != get_cell_column(edited_cells[-1]):
return
var row_start := get_cell_row(edited_cells[-1])
var row_end := get_cell_row(cell)
var edge_shift := -1 if row_start > row_end else 1
row_start += edge_shift
row_end += edge_shift
var column_editor := column_editors[column_index]
for i in range(row_start, row_end, edge_shift):
var cur_cell := Vector2i(column_index, i)
var cur_cell_node := get_cell_node_from_position(cur_cell)
if cur_cell not in edited_cells:
edited_cells.append(cur_cell)
var cur_cell_value = editor_view.io.get_value(editor_view.rows[cur_cell.y], editor_view.columns[cur_cell.x])
var cur_cell_text : String = column_editor.to_text(cur_cell_value)
edited_cells_text.append(cur_cell_text)
edit_cursor_positions.append(cur_cell_text.length())
if cur_cell_node == null or !cur_cell_node.visible or cur_cell_node.mouse_filter == MOUSE_FILTER_IGNORE:
# When showing several classes, empty cells will be non-selectable.
continue
column_editors[column_index].set_selected(cur_cell_node, true)
_selection_changed()
func rightclick_cells():
cells_rightclicked.emit(edited_cells)
func is_cell_node_selected(cell : Control) -> bool:
return get_cell_node_position(cell) in edited_cells
func is_cell_selected(cell : Vector2i) -> bool:
return cell in edited_cells
func can_select_cell(cell : Vector2i) -> bool:
if edited_cells.size() == 0:
return true
if (
get_cell_column(cell)
!= get_cell_column(edited_cells[0])
):
return false
return !cell in edited_cells
func get_cell_node_from_position(cell_pos : Vector2i) -> Control:
var cell_index := (cell_pos.y - editor_view.first_row) * editor_view.columns.size() + cell_pos.x
if cell_index < 0 or cell_index >= editor_view.node_table_root.get_child_count():
return null
return editor_view.node_table_root.get_child(cell_index)
func get_cell_node_position(cell : Control) -> Vector2i:
var col_count := editor_view.columns.size()
var cell_index := cell.get_index()
return Vector2i(cell_index % col_count, cell_index / col_count + editor_view.first_row)
func get_cell_column(cell : Vector2i) -> int:
return cell.x
func get_cell_row(cell : Vector2i) -> int:
return cell.y
func get_edited_rows() -> Array[int]:
var rows : Array[int] = []
rows.resize(edited_cells.size())
for i in rows.size():
rows[i] = get_cell_row(edited_cells[i])
return rows
func clipboard_paste():
if column_editors[edited_cells[0].x].is_text():
editor_view.set_edited_cells_values(
TextEditingUtilsClass.multi_paste(
edited_cells_text,
edit_cursor_positions,
)
)
elif DisplayServer.clipboard_has():
var values := []
values.resize(edited_cells.size())
var pasted_lines := DisplayServer.clipboard_get().replace("\r", "").split("\n")
var paste_each_line := pasted_lines.size() == values.size()
for i in values.size():
values[i] = str_to_var(
pasted_lines[i] if paste_each_line else DisplayServer.clipboard_get()
)
editor_view.set_edited_cells_values(values)
func _selection_changed():
queue_redraw()
cells_selected.emit(edited_cells)
func _set_visible_selected(state : bool):
for x in edited_cells:
var cell_node := get_cell_node_from_position(x)
if cell_node != null:
column_editors[get_cell_column(x)].set_selected(cell_node, state)
func _add_cell_to_selection(cell : Vector2i):
if !edited_cells.has(cell):
edited_cells.append(cell)
var column_editor := column_editors[get_cell_column(cell)]
var cell_node := get_cell_node_from_position(cell)
if cell_node != null:
column_editor.set_selected(cell_node, true)
var cell_value = editor_view.io.get_value(editor_view.rows[cell.y], editor_view.columns[cell.x])
var text_value : String = column_editor.to_text(cell_value)
edited_cells_text.append(text_value)
edit_cursor_positions.append(text_value.length())
func _update_selected_cells_text():
if edited_cells_text.size() == 0:
return
var column_editor := column_editors[get_cell_column(edited_cells[0])]
if !column_editor.text_update_on_edit():
return
for i in edited_cells.size():
edited_cells_text[i] = column_editor.to_text(editor_view.io.get_value(
editor_view.rows[edited_cells[i].y],
editor_view.columns[edited_cells[i].x],
))
edit_cursor_positions[i] = edited_cells_text[i].length()
func _try_open_docks(cell : Vector2i):
var column_index := get_cell_column(cell)
var row = editor_view.rows[get_cell_row(cell)]
var column := editor_view.columns[column_index]
var type := editor_view.column_types[column_index]
var hints := editor_view.column_hints[column_index]
for x in node_property_editors.get_children():
x.visible = x.try_edit_value(editor_view.io.get_value(row, column), type, hints)
x.get_node(x.path_property_name).text = column
func _on_inspector_property_edited(property : StringName):
if !editor_view.is_visible_in_tree(): return
if inspector_resource != editor_view.editor_plugin.get_editor_interface().get_inspector().get_edited_object():
return
if editor_view.columns[get_cell_column(edited_cells[0])] != property:
var columns := editor_view.columns
var previously_edited := edited_cells.duplicate()
var new_column := columns.find(property)
deselect_all_cells()
for i in previously_edited.size():
_add_cell_to_selection(Vector2i(new_column, previously_edited[i].y))
var new_value = inspector_resource[property]
var values := []
values.resize(edited_cells.size())
values.fill(new_value)
if new_value is Resource and new_value.resource_path == "":
for i in values.size():
values[i] = new_value.duplicate()
editor_view.set_edited_cells_values.call_deferred(values)
_try_open_docks(edited_cells[0])

View File

@@ -0,0 +1 @@
uid://ce5uido7e30hd

View File

@@ -0,0 +1,72 @@
@tool
extends HBoxContainer
var manager : Control
func set_label(label : String):
$"Button".text = label.capitalize()
$"Button".tooltip_text = label + "\nClick to sort."
func _ready():
$"Button".gui_input.connect(_on_main_gui_input)
$"Button2".about_to_popup.connect(_on_about_to_popup)
$"Button2".get_popup().id_pressed.connect(_on_list_id_pressed)
func _on_about_to_popup():
var menu_popup : PopupMenu = $"Button2".get_popup()
menu_popup.clear()
menu_popup.add_item("Select All", 0)
menu_popup.add_item("Hide", 1)
if !manager.editor_view.column_can_solo_open(get_index()):
menu_popup.add_item("(not a Resource property)", 2)
menu_popup.set_item_disabled(2, true)
menu_popup.add_separator("", 3)
else:
menu_popup.add_item("Open Sub-Resources of Column", 2)
if manager.editor_view.get_edited_cells_values().size() == 0 or manager.editor_view.get_selected_column() != get_index():
menu_popup.add_item("(none selected)", 3)
menu_popup.set_item_disabled(3, true)
else:
menu_popup.add_item("Open Sub-Resources in Selection", 3)
func _on_main_gui_input(event : InputEvent):
if event is InputEventMouseButton and event.pressed:
var popup : Popup = $"Button2".get_popup()
if event.button_index == MOUSE_BUTTON_RIGHT:
_on_about_to_popup()
popup.visible = !popup.visible
popup.size = Vector2.ZERO
popup.position = Vector2i(get_global_mouse_position()) + get_viewport().position
else:
popup.visible = false
func _on_list_id_pressed(id : int):
match id:
0:
manager.select_column(get_index())
1:
manager.hide_column(get_index())
2:
manager.editor_view.column_solo_open(get_index())
3:
var resources_to_open_unique := {}
for x in manager.editor_view.get_edited_cells_values():
if x is Array:
for y in x:
resources_to_open_unique[y] = true
if x is Resource:
resources_to_open_unique[x] = true
if resources_to_open_unique.size() > 0:
manager.editor_view.display_resources(resources_to_open_unique.keys())

View File

@@ -0,0 +1 @@
uid://dxcn2ds2d4qcx

View File

@@ -0,0 +1,23 @@
[gd_scene load_steps=3 format=3 uid="uid://d1s6oihqedvo5"]
[ext_resource type="Script" uid="uid://dxcn2ds2d4qcx" path="res://addons/resources_spreadsheet_view/main_screen/table_header.gd" id="1_5fd1m"]
[ext_resource type="Script" uid="uid://dyxdmrhopt5i0" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_0ymob"]
[node name="Header" type="HBoxContainer"]
offset_right = 179.0
offset_bottom = 31.0
size_flags_horizontal = 3
script = ExtResource("1_5fd1m")
[node name="Button" type="Button" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 0
text = "resource_name"
clip_text = true
[node name="Button2" type="MenuButton" parent="."]
layout_mode = 2
size_flags_horizontal = 9
script = ExtResource("2_0ymob")
icon_name = "ArrowDown"

View File

@@ -0,0 +1,118 @@
@tool
extends HBoxContainer
@onready var node_editor_view_root : Control = $"../../../.."
var rows_per_page := 50
var current_page := 0
func update_page_count(array : Array) -> int:
var page_count : int = (node_editor_view_root.rows.size() - 1) / rows_per_page + 1
node_editor_view_root.first_row = min(current_page, page_count) * rows_per_page
node_editor_view_root.last_row = min(node_editor_view_root.first_row + rows_per_page, array.size())
return page_count
func _on_grid_updated():
if node_editor_view_root.rows.size() == 0:
return
visible = true
var page_count := update_page_count(node_editor_view_root.rows)
var pagelist_node := $"Scroll/Pagelist"
for x in pagelist_node.get_children():
x.queue_free()
var button_group := ButtonGroup.new()
var btns := []
btns.resize(page_count)
for i in page_count:
var btn := Button.new()
btns[i] = btn
btn.toggle_mode = true
btn.button_group = button_group
btn.text = str(i + 1)
btn.pressed.connect(_on_button_pressed.bind(btn))
btn.size_flags_vertical = SIZE_SHRINK_CENTER
pagelist_node.add_child(btn)
var pagelist_line := HSeparator.new()
pagelist_line.size_flags_horizontal = SIZE_EXPAND_FILL
pagelist_node.add_child(pagelist_line)
btns[current_page].button_pressed = true
var sort_property : StringName = node_editor_view_root.sorting_by
if sort_property == "": sort_property = "resource_path"
var sort_type : int = node_editor_view_root.column_types[node_editor_view_root.columns.find(sort_property)]
var property_values := []
property_values.resize(page_count)
if(node_editor_view_root.rows.size() == 0):
return
for i in page_count:
property_values[i] = node_editor_view_root.rows[i * rows_per_page].get(sort_property)
if sort_type == TYPE_FLOAT or sort_type == TYPE_INT:
for i in page_count:
btns[i].text = str(property_values[i])
elif sort_type == TYPE_COLOR:
for i in page_count:
btns[i].self_modulate = property_values[i] * 0.75 + Color(0.25, 0.25, 0.25, 1.0)
elif sort_type == TYPE_STRING:
var strings := []
strings.resize(page_count)
for i in page_count:
strings[i] = property_values[i].get_file()
if strings[i] == "":
strings[i] = str(i)
_fill_buttons_with_prefixes(btns, strings, page_count)
elif sort_type == TYPE_OBJECT:
var strings := []
strings.resize(page_count + 1)
for i in page_count:
if is_instance_valid(property_values[i]):
strings[i] = property_values[i].resource_path.get_file()
_fill_buttons_with_prefixes(btns, strings, page_count)
func _fill_buttons_with_prefixes(btns : Array, strings : Array, page_count : int):
for i in page_count:
if strings[i] == null:
continue
if i == 0:
btns[0].text = strings[0][0]
continue
for j in strings[i].length():
if strings[i].unicode_at(j) != strings[i - 1].unicode_at(j):
btns[i].text = strings[i].left(j + 1)
btns[i - 1].text = strings[i - 1].left(max(j + 1, btns[i - 1].text.length()))
break
for i in page_count - 1:
btns[i].text = btns[i].text + "-" + btns[i + 1].text
btns[page_count - 1].text += "-[End]"
func _on_button_pressed(button):
button.button_pressed = true
current_page = button.get_index()
_update_view()
func _on_LineEdit_value_changed(value):
rows_per_page = value
current_page = 0
_update_view()
func _update_view():
node_editor_view_root.refresh(false)

View File

@@ -0,0 +1 @@
uid://dc76nawgefkbh