Port code from 3.0, but not the features

This commit is contained in:
don-tnowe 2023-01-23 14:01:03 +02:00
parent c56a638340
commit 91e006571d
26 changed files with 1648 additions and 224 deletions

View File

@ -32,6 +32,8 @@ Possible inputs:
- `Home/End` - Move cursor to start/end of cell
- `Ctrl + <move/erase> / Cmd + <move/erase>` - Move through / Erase whole word
- `Ctrl/Cmd + C/V` - Copy cells / Paste text into cells
- `Ctrl/Cmd + R` - Rename resource
- `Ctrl/Cmd + D` - Duplicate selected rows
- `Ctrl/Cmd + (Shift) + Z` - The Savior
If clipboard contains as many lines as there are cells selected, each line is pasted into a separate cell.
@ -41,8 +43,10 @@ To add support of more datatypes, check out the `typed_cells` and `typed_editors
#
Made by Don Tnowe in 2022.
[https://redbladegames.netlify.app]()
[My Website](https://redbladegames.netlify.app)
[https://twitter.com/don_tnowe]()
[Itch](https://don-tnowe.itch.io)
Copying and Modiication is allowed in accordance to the MIT license, full text is included.
[Twitter](https://twitter.com/don_tnowe)
Copying and Modification is allowed in accordance to the MIT license, full text is included.

View File

@ -2,6 +2,8 @@
extends Control
signal grid_updated()
signal cells_selected(cells)
signal cells_context(cells)
@export var table_header_scene : PackedScene
@export var cell_editor_classes : Array[Script] = []
@ -22,7 +24,6 @@ var recent_paths := []
var save_data_path : String = get_script().resource_path.get_base_dir() + "/saved_state.json"
var sorting_by := ""
var sorting_reverse := false
var undo_redo_version := 0
var all_cell_editors := []
@ -37,9 +38,10 @@ var remembered_paths := {}
var edited_cells := []
var edited_cells_text := []
var edit_cursor_positions := []
var inspector_resource : Resource
var search_cond : RefCounted
var my_undo_redo : UndoRedo
var io : RefCounted
var hidden_columns := {}
var first_row := 0
@ -48,7 +50,6 @@ var last_row := 0
func _ready():
node_recent_paths.clear()
my_undo_redo = editor_plugin.get_undo_redo().get_history_undo_redo(editor_plugin.get_undo_redo().get_object_history_id(self))
editor_interface.get_resource_filesystem().filesystem_changed.connect(_on_filesystem_changed)
editor_interface.get_inspector().property_edited.connect(_on_inspector_property_edited)
node_hide_columns_button.get_popup().id_pressed.connect(_on_visible_cols_id_pressed)
@ -69,6 +70,9 @@ func _ready():
for x in cell_editor_classes:
all_cell_editors.append(x.new())
all_cell_editors[all_cell_editors.size() - 1].hint_strings_array = column_hint_strings
node_recent_paths.selected = 0
display_folder(recent_paths[0], "resource_name", false, true)
func _on_filesystem_changed():
@ -90,18 +94,23 @@ func _on_filesystem_changed():
break
func display_folder(folderpath : String, sort_by : String = "", sort_reverse : bool = false, force_rebuild : bool = false):
func display_folder(folderpath : String, sort_by : String = "", sort_reverse : bool = false, force_rebuild : bool = false, is_echo : bool = false):
if folderpath == "": return # Root folder resources tend to have MANY properties.
$"HeaderContentSplit/MarginContainer/FooterContentSplit/Panel/Label".visible = false
if !folderpath.ends_with("/"):
folderpath += "/"
if folderpath.get_extension() == "":
folderpath = folderpath.trim_suffix("/") + "/"
if folderpath.ends_with(".tres") and !folderpath.ends_with(SpreadsheetImport.SUFFIX):
folderpath = folderpath.get_base_dir() + "/"
if search_cond == null:
_on_search_cond_text_submitted("true")
add_path_to_recent(folderpath)
first_row = node_page_manager.first_row
last_row = min(node_page_manager.last_row, rows.size())
_load_resources_from_folder(folderpath, sort_by, sort_reverse)
if columns.size() == 0: return
node_folder_path.text = folderpath
@ -114,7 +123,7 @@ func display_folder(folderpath : String, sort_by : String = "", sort_reverse : b
_update_hidden_columns()
_update_column_sizes()
await get_tree().process_frame
await get_tree().create_timer(0.25).timeout
if node_table_root.get_child_count() == 0:
display_folder(folderpath, sort_by, sort_reverse, force_rebuild)
@ -126,65 +135,50 @@ func refresh(force_rebuild : bool = true):
display_folder(current_path, sorting_by, sorting_reverse, force_rebuild)
func _load_resources_from_folder(folderpath : String, sort_by : String, sort_reverse : bool):
var dir = DirAccess.open(folderpath)
dir.list_dir_begin()
func _load_resources_from_folder(path : String, sort_by : String, sort_reverse : bool):
if path.ends_with("/"):
io = SpreadsheetEditFormatTres.new()
rows.clear()
remembered_paths.clear()
var cur_dir_script : Script = null
var filepath = dir.get_next()
var res : Resource
while filepath != "":
remembered_paths[folderpath + filepath] = null
if filepath.ends_with(".tres"):
filepath = folderpath + filepath
res = load(filepath)
if !is_instance_valid(cur_dir_script):
columns.clear()
column_types.clear()
column_hints.clear()
column_hint_strings.clear()
column_editors.clear()
var column_index = -1
for x in res.get_property_list():
if x["usage"] & PROPERTY_USAGE_EDITOR != 0 and x["name"] != "script":
column_index += 1
columns.append(x["name"])
column_types.append(x["type"])
column_hints.append(x["hint"])
column_hint_strings.append(x["hint_string"].split(","))
for y in all_cell_editors:
if y.can_edit_value(res.get(x["name"]), x["type"], x["hint"], column_index):
column_editors.append(y)
break
cur_dir_script = res.get_script()
if !(sort_by in res):
sort_by = "resource_path"
if res.get_script() == cur_dir_script:
_insert_row_sorted(res, rows, sort_by, sort_reverse)
remembered_paths[res.resource_path] = res
filepath = dir.get_next()
else:
io = load(path).view_script.new()
io.editor_view = self
rows = io.import_from_path(path, insert_row_sorted, sort_by, sort_reverse)
func _insert_row_sorted(res : Resource, rows : Array, sort_by : String, sort_reverse : bool):
func fill_property_data(res):
columns.clear()
column_types.clear()
column_hints.clear()
column_hint_strings.clear()
column_editors.clear()
var column_index = -1
for x in res.get_property_list():
if x["usage"] & PROPERTY_USAGE_EDITOR != 0 and x["name"] != "script":
column_index += 1
columns.append(x["name"])
column_types.append(x["type"])
column_hints.append(x["hint"])
column_hint_strings.append(x["hint_string"].split(","))
for y in all_cell_editors:
if y.can_edit_value(io.get_value(res, x["name"]), x["type"], x["hint"], column_index):
column_editors.append(y)
break
func insert_row_sorted(res : Resource, rows : Array, sort_by : String, sort_reverse : bool):
if !search_cond.can_show(res, rows.size()):
return
for i in rows.size():
if sort_reverse == _compare_values(res.get(sort_by), rows[i].get(sort_by)):
if sort_reverse == compare_values(io.get_value(res, sort_by), io.get_value(rows[i], sort_by)):
rows.insert(i, res)
return
rows.append(res)
func _compare_values(a, b) -> bool:
func compare_values(a, b) -> bool:
if a == null or b == null: return b == null
if a is Color:
return a.h > b.h if a.h != b.h else a.v > b.v
@ -267,7 +261,6 @@ func _update_column_sizes():
var min_width := 0
var cell : Control
node_columns.get_parent().custom_minimum_size.y = column_headers[0].size.y
for i in column_headers.size():
var header = column_headers[i]
cell = node_table_root.get_child(i)
@ -289,6 +282,8 @@ func _update_column_sizes():
node_columns.show()
await get_tree().process_frame
node_columns.get_parent().custom_minimum_size.y = column_headers[0].size.y
for i in column_headers.size():
column_headers[i].position.x = node_table_root.get_child(i).position.x
column_headers[i].size.x = node_table_root.get_child(i).size.x
@ -306,17 +301,17 @@ func _update_row(row_index : int, color_rows : bool = true):
else:
current_node = node_table_root.get_child((row_index - first_row) * columns.size() + i)
current_node.tooltip_text = (
TextEditingUtils.string_snake_to_naming_case(columns[i])
columns[i].capitalize()
+ "\n---\n"
+ "Of " + rows[row_index].resource_path.get_file().get_basename()
)
column_editors[i].set_value(current_node, rows[row_index].get(columns[i]))
column_editors[i].set_value(current_node, io.get_value(rows[row_index], columns[i]))
if columns[i] == "resource_path":
column_editors[i].set_value(current_node, current_node.text.get_file().get_basename())
if color_rows and column_types[i] == TYPE_COLOR:
next_color = rows[row_index].get(columns[i])
next_color = io.get_value(rows[row_index], columns[i])
column_editors[i].set_color(current_node, next_color)
@ -383,7 +378,6 @@ func save_data():
func _on_path_text_submitted(new_text : String = ""):
if new_text != "":
current_path = new_text
add_path_to_recent(new_text)
display_folder(new_text, "", false, true)
else:
@ -393,12 +387,11 @@ func _on_path_text_submitted(new_text : String = ""):
func _on_RecentPaths_item_selected(index : int):
current_path = recent_paths[index]
node_folder_path.text = recent_paths[index]
display_folder(current_path)
display_folder(current_path, sorting_by, sorting_reverse, true)
func _on_FileDialog_dir_selected(dir : String):
node_folder_path.text = dir
add_path_to_recent(dir)
display_folder(dir)
@ -409,6 +402,7 @@ func deselect_all_cells():
edited_cells.clear()
edited_cells_text.clear()
edit_cursor_positions.clear()
cells_selected.emit([])
func deselect_cell(cell : Control):
@ -420,6 +414,8 @@ func deselect_cell(cell : Control):
if edited_cells_text.size() != 0:
edited_cells_text.remove_at(idx)
edit_cursor_positions.remove_at(idx)
cells_selected.emit(edited_cells)
func select_cell(cell : Control):
@ -431,6 +427,8 @@ func select_cell(cell : Control):
inspector_resource = rows[_get_cell_row(cell)]
editor_plugin.get_editor_interface().edit_resource(inspector_resource)
cells_selected.emit(edited_cells)
func select_cells_to(cell : Control):
var column_index := _get_cell_column(cell)
@ -456,6 +454,8 @@ func select_cells_to(cell : Control):
edited_cells_text.append(str(cur_cell.text))
edit_cursor_positions.append(cur_cell.text.length())
cells_selected.emit(edited_cells)
func select_column(column_index : int):
deselect_all_cells()
@ -486,7 +486,7 @@ func _try_open_docks(cell : Control):
var column_index = _get_cell_column(cell)
for x in node_property_editors.get_children():
x.visible = x.try_edit_value(
rows[_get_cell_row(cell)].get(columns[column_index]),
io.get_value(rows[_get_cell_row(cell)], columns[column_index]),
column_types[column_index],
column_hints[column_index]
)
@ -524,10 +524,34 @@ func set_edited_cells_values(new_cell_values : Array):
)
editor_plugin.undo_redo.commit_action()
editor_interface.get_resource_filesystem().scan()
undo_redo_version = my_undo_redo.get_version()
_update_column_sizes()
func rename_row(row, new_name):
if !has_row_names(): return
io.rename_row(row, new_name)
refresh()
func duplicate_selected_rows(new_name : String):
io.duplicate_rows(_get_edited_cells_resources(), new_name)
refresh()
func delete_selected_rows():
io.delete_rows(_get_edited_cells_resources())
refresh()
func has_row_names():
return io.has_row_names()
func get_last_selected_row():
return rows[_get_cell_row(edited_cells[-1])]
func _update_selected_cells_text():
if edited_cells_text.size() == 0:
return
@ -542,13 +566,13 @@ func get_edited_cells_values() -> Array:
var column_index := _get_cell_column(edited_cells[0])
var cell_editor = column_editors[column_index]
for i in arr.size():
arr[i] = rows[_get_cell_row(arr[i])].get(columns[column_index])
arr[i] = io.get_value(rows[_get_cell_row(arr[i])], columns[column_index])
return arr
func get_cell_value(cell : Control):
return rows[_get_cell_row(cell)].get(columns[_get_cell_column(cell)])
return io.get_value(rows[_get_cell_row(cell)], columns[_get_cell_column(cell)])
func _can_select_cell(cell : Control) -> bool:
@ -579,6 +603,13 @@ func _on_cell_gui_input(event : InputEvent, cell : Control):
if event is InputEventMouseButton:
grab_focus()
if event.button_index != MOUSE_BUTTON_LEFT:
if event.button_index == MOUSE_BUTTON_RIGHT && event.is_pressed():
if !cell in edited_cells:
deselect_all_cells()
select_cell(cell)
cells_context.emit(edited_cells)
return
if event.pressed:
@ -600,6 +631,9 @@ func _on_cell_gui_input(event : InputEvent, cell : Control):
func _gui_input(event : InputEvent):
if event is InputEventMouseButton:
if event.button_index != MOUSE_BUTTON_LEFT:
if event.button_index == MOUSE_BUTTON_RIGHT && event.is_pressed():
cells_context.emit(edited_cells)
return
grab_focus()
@ -619,24 +653,24 @@ func _input(event : InputEvent):
return
# Ctrl + Z (before, and instead of, committing the action!)
if Input.is_key_pressed(KEY_CTRL) and event.keycode == KEY_Z:
if Input.is_key_pressed(KEY_SHIFT):
my_undo_redo.redo()
# Ctrl + z
else:
my_undo_redo.undo()
return
if Input.is_key_pressed(KEY_CTRL):
if event.keycode == KEY_Z:
if Input.is_key_pressed(KEY_SHIFT):
editor_plugin.undo_redo.redo()
# Ctrl + z (smol)
else:
editor_plugin.undo_redo.undo()
return
# This shortcut is used by Godot as well.
if event.keycode == KEY_Y:
editor_plugin.undo_redo.redo()
return
# This shortcut is used by Godot as well.
if Input.is_key_pressed(KEY_CTRL) and event.keycode == KEY_Y:
my_undo_redo.redo()
return
_key_specific_action(event)
grab_focus()
editor_interface.get_resource_filesystem().scan()
undo_redo_version = my_undo_redo.get_version()
func _key_specific_action(event : InputEvent):
@ -685,6 +719,7 @@ func _key_specific_action(event : InputEvent):
# Ctrl + C (so you can edit in a proper text editor instead of this wacky nonsense)
elif ctrl_pressed and event.keycode == KEY_C:
TextEditingUtils.multi_copy(edited_cells_text)
get_tree().set_input_as_handled()
# The following actions do not work on non-editable cells.
if !column_editors[column].is_text() or columns[column] == "resource_path":
@ -695,6 +730,7 @@ func _key_specific_action(event : InputEvent):
set_edited_cells_values(TextEditingUtils.multi_paste(
edited_cells_text, edit_cursor_positions
))
get_tree().set_input_as_handled()
# ERASING
elif event.keycode == KEY_BACKSPACE:
@ -734,17 +770,24 @@ func _move_selection_on_grid(move_h : int, move_v : int):
func _update_resources(update_rows : Array, update_cells : Array, update_column : int, values : Array):
var saved_indices = []
saved_indices.resize(update_rows.size())
for i in update_rows.size():
var row = _get_cell_row(update_cells[i])
saved_indices[i] = row
column_editors[update_column].set_value(update_cells[i], values[i])
if values[i] is String:
values[i] = _try_convert(values[i], column_types[update_column])
if values[i] == null:
continue
update_rows[i].set(columns[update_column], values[i])
ResourceSaver.save(update_rows[i])
io.set_value(
update_rows[i],
columns[update_column],
values[i],
row
)
if column_types[update_column] == TYPE_COLOR:
for j in columns.size() - update_column:
if j != 0 and column_types[j + update_column] == TYPE_COLOR:
@ -757,6 +800,7 @@ func _update_resources(update_rows : Array, update_cells : Array, update_column
values[i]
)
io.save_entries(rows, saved_indices)
_update_column_sizes()
@ -771,7 +815,8 @@ func _try_convert(value, type):
func _get_edited_cells_resources() -> Array:
var arr := edited_cells.duplicate()
var arr := []
arr.resize(edited_cells.size())
for i in arr.size():
arr[i] = rows[_get_cell_row(edited_cells[i])]
@ -811,8 +856,6 @@ func _on_inspector_property_edited(property : String):
if !visible: return
if inspector_resource == null: return
undo_redo_version = my_undo_redo.get_version()
var value = inspector_resource.get(property)
var values = []
values.resize(edited_cells.size())
@ -847,7 +890,7 @@ func _on_visible_cols_about_to_popup():
popup.hide_on_checkable_item_selection = false
for i in columns.size():
popup.add_check_item(TextEditingUtils.string_snake_to_naming_case(columns[i]), i)
popup.add_check_item(columns[i].capitalize(), i)
popup.set_item_checked(i, hidden_columns[current_path].has(columns[i]))

View File

@ -1,11 +1,11 @@
[gd_scene load_steps=23 format=3 uid="uid://dpdgrocww8a51"]
[gd_scene load_steps=21 format=3 uid="uid://blf43y5mcinf7"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_view.gd" id="1"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd" id="2"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd" id="3"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="4"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd" id="5"]
[ext_resource type="PackedScene" uid="uid://bya5ugi40ptxl" path="res://addons/resources_spreadsheet_view/table_header.tscn" id="6"]
[ext_resource type="PackedScene" uid="uid://d1s6oihqedvo5" path="res://addons/resources_spreadsheet_view/table_header.tscn" id="6"]
[ext_resource type="PackedScene" uid="uid://b3a3bo6cfyh5t" path="res://addons/resources_spreadsheet_view/typed_editors/dock_color.tscn" id="7"]
[ext_resource type="PackedScene" path="res://addons/resources_spreadsheet_view/typed_editors/dock_number.tscn" id="8"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd" id="8_2kaah"]
@ -26,18 +26,6 @@ colors = PackedColorArray(1, 1, 1, 0.490196, 1, 1, 1, 0.0458716, 1, 1, 1, 0)
[sub_resource type="GradientTexture2D" id="GradientTexture2D_18il8"]
gradient = SubResource("Gradient_8kp6w")
[sub_resource type="Image" id="Image_wmhif"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_we5a3"]
image = SubResource("Image_wmhif")
[node name="Control" type="MarginContainer" node_paths=PackedStringArray("node_folder_path", "node_recent_paths", "node_table_root", "node_property_editors", "node_columns", "node_hide_columns_button", "node_page_manager")]
anchors_preset = 15
anchor_right = 1.0
@ -143,7 +131,7 @@ offset_right = 48.0
grow_horizontal = 0
grow_vertical = 2
texture = SubResource("GradientTexture2D_18il8")
ignore_texture_size = true
expand_mode = 1
[node name="ColorRect3" type="Control" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(2, 0)
@ -162,14 +150,12 @@ caret_blink = true
[node name="SelectDir" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
layout_mode = 2
tooltip_text = "Open Folder"
icon = SubResource("ImageTexture_we5a3")
script = ExtResource("4")
icon_name = "Folder"
[node name="DeletePath" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer/HBoxContainer"]
layout_mode = 2
tooltip_text = "Remove Path from Recent"
icon = SubResource("ImageTexture_we5a3")
script = ExtResource("4")
icon_name = "Remove"
@ -212,7 +198,7 @@ offset_right = 48.0
grow_horizontal = 0
grow_vertical = 2
texture = SubResource("GradientTexture2D_18il8")
ignore_texture_size = true
expand_mode = 1
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/HBoxContainer"]
layout_mode = 2
@ -242,7 +228,7 @@ offset_right = 48.0
grow_horizontal = 0
grow_vertical = 2
texture = SubResource("GradientTexture2D_18il8")
ignore_texture_size = true
expand_mode = 1
[node name="Label" type="Label" parent="HeaderContentSplit/VBoxContainer/Search/Search"]
layout_mode = 2
@ -306,7 +292,6 @@ text = "Grid"
[node name="Refresh" type="Button" parent="HeaderContentSplit/VBoxContainer/HBoxContainer3"]
layout_mode = 2
tooltip_text = "Refresh"
icon = SubResource("ImageTexture_we5a3")
script = ExtResource("4")
icon_name = "Loop"
@ -348,6 +333,7 @@ layout_mode = 0
theme_override_constants/separation = 0
[node name="Sep2" type="Control" parent="HeaderContentSplit/VBoxContainer"]
visible = false
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="HeaderContentSplit"]

View File

@ -0,0 +1,36 @@
class_name SpreadsheetEditFormat
extends RefCounted
var editor_view : Control
## Override to define reading behaviour.
func get_value(entry, key : String):
pass
## Override to define writing behaviour. This is NOT supposed to save - use `save_entries`.
func set_value(entry, key : String, value, index : int):
pass
## Override to define how the data gets saved.
func save_entries(all_entries : Array, indices : Array):
pass
## Override to allow editing rows from the Inspector.
func create_resource(entry) -> Resource:
return Resource.new()
## Override to define duplication behaviour. `name_input` should be a suffix if multiple entries, and full name if one.
func duplicate_rows(rows : Array, name_input : String):
pass
## Override to define removal behaviour.
func delete_rows(rows : Array):
pass
## Override with `return true` if `resource_path` is defined and the Rename butoon should show.
func has_row_names():
return false
## Override to define import behaviour. Must return the `rows` value for the editor view.
func import_from_path(folderpath : String, insert_func : Callable, sort_by : String, sort_reverse : bool = false) -> Array:
return []

View File

@ -0,0 +1,91 @@
class_name SpreadsheetEditFormatCsv
extends SpreadsheetEditFormatTres
var import_data
var csv_rows = []
var resource_original_positions = {}
var timer : SceneTreeTimer
func get_value(entry, key : String):
return entry.get(key)
func set_value(entry, key : String, value, index : int):
entry.set(key, value)
csv_rows[resource_original_positions[entry]] = import_data.resource_to_strings(entry)
func save_entries(all_entries : Array, indices : Array, repeat : bool = true):
if timer == null || timer.time_left <= 0.0:
var space_after_delimeter = import_data.delimeter.ends_with(" ")
var file = FileAccess.open(import_data.edited_path, FileAccess.WRITE)
for x in csv_rows:
if space_after_delimeter:
for i in x.size():
if i == 0: continue
x[i] = " " + x[i]
file.store_csv_line(x, import_data.delimeter[0])
file.close()
if repeat:
timer = editor_view.get_tree().create_timer(3.0)
timer.timeout.connect(save_entries.bind(all_entries, indices, false))
func create_resource(entry) -> Resource:
return entry
func duplicate_rows(rows : Array, name_input : String):
for x in rows:
var new_res = x.duplicate()
var index = resource_original_positions[x]
csv_rows.insert(index, import_data.resource_to_strings(new_res))
_bump_row_indices(index + 1, 1)
resource_original_positions[new_res] = index + 1
save_entries([], [])
func delete_rows(rows):
for x in rows:
var index = resource_original_positions[x]
csv_rows.remove(index)
_bump_row_indices(index, -1)
resource_original_positions.erase(x)
save_entries([], [])
func has_row_names():
return false
func _bump_row_indices(from : int, increment : int = 1):
for k in resource_original_positions:
if resource_original_positions[k] >= from:
resource_original_positions[k] += increment
func import_from_path(path : String, insert_func : Callable, sort_by : String, sort_reverse : bool = false) -> Array:
import_data = load(path)
var file = FileAccess.open(import_data.edited_path, FileAccess.READ)
csv_rows = SpreadsheetImportFormatCsv.import_as_arrays(import_data)
var rows := []
var res : Resource
resource_original_positions.clear()
for i in csv_rows.size():
if import_data.remove_first_row and i == 0:
continue
res = import_data.strings_to_resource(csv_rows[i])
res.resource_path = ""
insert_func.call(res, rows, sort_by, sort_reverse)
resource_original_positions[res] = i
editor_view.fill_property_data(rows[0])
file.close()
return rows

View File

@ -0,0 +1,79 @@
class_name SpreadsheetEditFormatTres
extends SpreadsheetEditFormat
func get_value(entry, key : String):
return entry.get(key)
func set_value(entry, key : String, value, index : int):
entry.set(key, value)
func save_entries(all_entries : Array, indices : Array):
for x in indices:
ResourceSaver.save(all_entries[x])
func create_resource(entry) -> Resource:
return entry
func duplicate_rows(rows : Array, name_input : String):
if rows.size() == 1:
var new_row = rows[0].duplicate()
new_row.resource_path = rows[0].resource_path.get_base_dir() + "/" + name_input + ".tres"
ResourceSaver.save(new_row)
return
var new_row
for x in rows:
new_row = x.duplicate()
new_row.resource_path = x.resource_path.get_basename() + name_input + ".tres"
ResourceSaver.save(new_row)
func rename_row(row, new_name : String):
var new_row = row
DirAccess.open("res://").remove(row.resource_path)
new_row.resource_path = row.resource_path.get_base_dir() + "/" + new_name + ".tres"
ResourceSaver.save(new_row)
func delete_rows(rows):
for x in rows:
DirAccess.open("res://").remove(x.resource_path)
func has_row_names():
return true
func import_from_path(folderpath : String, insert_func : Callable, sort_by : String, sort_reverse : bool = false) -> Array:
var rows := []
var dir := DirAccess.open(folderpath)
dir.list_dir_begin()
editor_view.remembered_paths.clear()
var cur_dir_script : Script = null
var filepath = dir.get_next()
var res : Resource
while filepath != "":
if filepath.ends_with(".tres"):
filepath = folderpath + filepath
res = load(filepath)
if !is_instance_valid(cur_dir_script):
editor_view.fill_property_data(res)
cur_dir_script = res.get_script()
if !(sort_by in res):
sort_by = "resource_path"
if res.get_script() == cur_dir_script:
insert_func.call(res, rows, sort_by, sort_reverse)
editor_view.remembered_paths[res.resource_path] = res
filepath = dir.get_next()
return rows

View File

@ -0,0 +1,30 @@
class_name SpreadsheetExportFormatCsv
extends RefCounted
static func export_to_file(entries_array : Array, column_names : Array, into_path : String, import_data):
var file = FileAccess.open(into_path, FileAccess.WRITE)
var line = PackedStringArray()
var space_after_delimeter = import_data.delimeter.ends_with(" ")
import_data.prop_names = column_names
import_data.prop_types = import_data.get_resource_property_types(entries_array[0], column_names)
import_data.resource_path = ""
line.resize(column_names.size())
if import_data.remove_first_row:
for j in column_names.size():
line[j] = column_names[j]
if space_after_delimeter and j != 0:
line[j] = " " + line[j]
file.store_csv_line(line, import_data.delimeter[0])
for i in entries_array.size():
for j in column_names.size():
line[j] = import_data.property_to_string((entries_array[i].get(column_names[j])), j)
if space_after_delimeter and j != 0:
line[j] = " " + line[j]
file.store_csv_line(line, import_data.delimeter[0])
file.close()

View File

@ -0,0 +1,49 @@
class_name SpreadsheetImportFormatCsv
extends RefCounted
static func can_edit_path(path : String):
return path.ends_with(".csv")
static func import_as_arrays(import_data) -> Array:
var file = FileAccess.open(import_data.edited_path, FileAccess.READ)
import_data.delimeter = ";"
var text_lines := [file.get_line().split(import_data.delimeter)]
var space_after_delimeter = false
var line = text_lines[0]
if line.size() == 1:
import_data.delimeter = ","
line = line[0].split(import_data.delimeter)
text_lines[0] = line
if line[1].begins_with(" "):
for i in line.size():
line[i] = line[i].trim_prefix(" ")
text_lines[0] = line
space_after_delimeter = true
import_data.delimeter += " "
while !file.eof_reached():
line = file.get_csv_line(import_data.delimeter[0])
if space_after_delimeter:
for i in line.size():
line[i] = line[i].trim_prefix(" ")
if line.size() == text_lines[0].size():
text_lines.append(line)
elif line.size() != 1:
line.resize(text_lines[0].size())
text_lines.append(line)
file.close()
var entries = []
entries.resize(text_lines.size())
for i in entries.size():
entries[i] = text_lines[i]
return entries

View File

@ -0,0 +1,212 @@
@tool
extends Window
@export var prop_list_item_scene : PackedScene
@export var formats_export : Array[Script]
@export var formats_import : Array[Script]
@onready var editor_view := $"../.."
@onready var node_filename_options := $"TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/OptionButton"
@onready var node_classname_field := $"TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/LineEdit"
@onready var node_filename_props := $"TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/OptionButton"
@onready var prop_list := $"TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer"
var entries := []
var property_used_as_filename := 0
var import_data : SpreadsheetImport
func _ready():
var create_file_button = Button.new()
$"../FileDialogText".get_child(3).get_child(3).add_child(create_file_button)
create_file_button.get_parent().move_child(create_file_button, 2)
create_file_button.text = "Create File"
create_file_button.visible = true
create_file_button.icon = get_theme_icon("New", "EditorIcons")
create_file_button.pressed.connect(_on_create_file_pressed)
func _on_create_file_pressed():
var new_name = (
$"../FileDialogText".get_child(3).get_child(3).get_child(1).text
)
if new_name == "":
new_name += editor_view.current_path.get_base_dir().get_file()
var file = FileAccess.open(
$"../FileDialogText".get_child(3).get_child(0).get_child(4).text
+ "/"
+ new_name.get_basename() + ".csv", FileAccess.WRITE
)
file.close()
$"../FileDialogText".invalidate()
func _on_FileDialogText_file_selected(path : String):
import_data = SpreadsheetImport.new()
import_data.initialize(path)
_reset_controls()
_open_dialog(path)
popup_centered()
func _open_dialog(path : String):
node_classname_field.text = import_data.edited_path.get_file().get_basename()\
.capitalize().replace(" ", "")
import_data.script_classname = node_classname_field.text
for x in formats_import:
if x.new().can_edit_path(path):
entries = x.new().import_as_arrays(import_data)
_load_property_names()
_create_prop_editors()
$"TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/StyleSettingsI"._send_signal()
func _load_property_names():
import_data.prop_names = Array(entries[0])
import_data.prop_types.resize(import_data.prop_names.size())
import_data.prop_types.fill(4)
for i in import_data.prop_names.size():
import_data.prop_names[i] = entries[0][i]\
.replace("\"", "")\
.replace(" ", "_")\
.replace("-", "_")\
.replace(".", "_")\
.replace(",", "_")\
.replace("\t", "_")\
.replace("/", "_")\
.replace("\\", "_")\
.to_lower()
# Don't guess Ints automatically - further rows might have floats
if entries[1][i].is_valid_float():
import_data.prop_types[i] = SpreadsheetImport.PropType.FLOAT
elif entries[1][i].begins_with("res://"):
import_data.prop_types[i] = SpreadsheetImport.PropType.OBJECT
else:
import_data.prop_types[i] = SpreadsheetImport.PropType.STRING
node_filename_options.clear()
for i in import_data.prop_names.size():
node_filename_options.add_item(import_data.prop_names[i], i)
func _create_prop_editors():
for x in prop_list.get_children():
if !x is GridContainer: x.free()
for i in import_data.prop_names.size():
var new_node = prop_list_item_scene.instance()
prop_list.add_child(new_node)
new_node.display(import_data.prop_names[i], import_data.prop_types[i])
new_node.connect_all_signals(self, i)
func _generate_class(save_script = true):
save_script = true # Built-ins didn't work in 3.x, won't change because dont wanna test rn
import_data.new_script = import_data.generate_script(entries, save_script)
if save_script:
import_data.new_script.resource_path = import_data.edited_path.get_basename() + ".gd"
ResourceSaver.save(import_data.new_script)
# Because when instanced, objects have a copy of the script
import_data.new_script = load(import_data.edited_path.get_basename() + ".gd")
func _export_tres_folder():
DirAccess.open("res://").make_dir_recursive(import_data.edited_path.get_basename())
import_data.prop_used_as_filename = import_data.prop_names[property_used_as_filename]
var new_res : Resource
for i in entries.size():
if import_data.remove_first_row && i == 0:
continue
new_res = import_data.strings_to_resource(entries[i])
ResourceSaver.save(new_res)
func _on_import_to_tres_pressed():
hide()
_generate_class()
_export_tres_folder()
await get_tree().process_frame
editor_view.display_folder(import_data.edited_path.get_basename() + "/")
await get_tree().process_frame
editor_view.refresh()
func _on_import_edit_pressed():
hide()
_generate_class(false)
import_data.prop_used_as_filename = ""
import_data.save()
await get_tree().process_frame
editor_view.display_folder(import_data.resource_path)
editor_view.hidden_columns[editor_view.current_path] = {
"resource_path" : true,
"resource_local_to_scene" : true,
}
editor_view.save_data()
await get_tree().process_frame
editor_view.refresh()
func _on_export_csv_pressed():
hide()
var exported_cols = editor_view.columns.duplicate()
exported_cols.erase("resource_local_to_scene")
for x in editor_view.hidden_columns[editor_view.current_path].keys():
exported_cols.erase(x)
SpreadsheetExportFormatCsv.export_to_file(editor_view.rows, exported_cols, import_data.edited_path, import_data)
await get_tree().process_frame
editor_view.refresh()
# Input controls
func _on_classname_field_text_changed(new_text : String):
import_data.script_classname = new_text.replace(" ", "")
func _on_remove_first_row_toggled(button_pressed : bool):
import_data.remove_first_row = button_pressed
$"TabContainer/Export/HBoxContainer2/Button".pressed = true
$"TabContainer/Export/HBoxContainer3/CheckBox".pressed = true
func _on_filename_options_item_selected(index):
property_used_as_filename = index
func _on_list_item_type_selected(type : int, index : int):
import_data.prop_types[index] = type
func _on_list_item_name_changed(name : String, index : int):
import_data.prop_names[index] = name.replace(" ", "")
func _on_export_delimeter_pressed(del : String):
import_data.delimeter = del + import_data.delimeter.substr(1)
func _on_export_space_toggled(button_pressed : bool):
import_data.delimeter = (
import_data.delimeter[0]
if !button_pressed else
import_data.delimeter + " "
)
func _reset_controls():
$"TabContainer/Export/HBoxContainer2/CheckBox".pressed = false
_on_remove_first_row_toggled(true)
func _on_enum_format_changed(case, delimiter, bool_yes, bool_no):
import_data.enum_format = [case, delimiter, bool_yes, bool_no]

View File

@ -0,0 +1,172 @@
[gd_scene load_steps=5 format=3 uid="uid://b413igx28kkvb"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/import_export_dialog.gd" id="1"]
[ext_resource type="PackedScene" path="res://addons/resources_spreadsheet_view/import_export/property_list_item.tscn" id="2"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd" id="3"]
[ext_resource type="PackedScene" path="res://addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn" id="4"]
[node name="Control" type="Window"]
size = Vector2i(600, 373)
wrap_controls = true
min_size = Vector2i(600, 0)
script = ExtResource("1")
prop_list_item_scene = ExtResource("2")
formats_import = [ExtResource("3")]
[node name="TabContainer" type="TabContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -552.0
offset_bottom = -275.0
grow_horizontal = 2
grow_vertical = 2
use_hidden_tabs_for_min_size = true
[node name="Import" type="VBoxContainer" parent="TabContainer"]
layout_mode = 2
mouse_filter = 2
[node name="MarginContainer" type="MarginContainer" parent="TabContainer/Import"]
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="TabContainer/Import/MarginContainer"]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="TabContainer/Import/MarginContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="GridContainer" type="GridContainer" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer"]
layout_mode = 2
columns = 2
[node name="Label" type="Label" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
layout_mode = 2
text = "Use as filename:"
[node name="OptionButton" type="OptionButton" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label2" type="Label" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
layout_mode = 2
text = "Class Name"
[node name="LineEdit" type="LineEdit" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
layout_mode = 2
caret_blink = true
caret_blink_interval = 0.5
[node name="CheckBox" type="CheckBox" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
layout_mode = 2
text = "First row contains property names"
[node name="Control" type="Control" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
layout_mode = 2
[node name="Control2" type="Control" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer"]
visible = false
layout_mode = 2
[node name="StyleSettingsI" parent="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer" instance=ExtResource("4")]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Import"]
layout_mode = 2
mouse_filter = 2
alignment = 1
[node name="Ok2" type="Button" parent="TabContainer/Import/HBoxContainer"]
layout_mode = 2
text = "Confirm and edit"
[node name="Ok" type="Button" parent="TabContainer/Import/HBoxContainer"]
layout_mode = 2
text = "Convert to Resources and edit"
[node name="Cancel" type="Button" parent="TabContainer/Import/HBoxContainer"]
layout_mode = 2
text = "Cancel"
[node name="Control" type="Control" parent="TabContainer/Import"]
layout_mode = 2
mouse_filter = 2
[node name="Export" type="VBoxContainer" parent="TabContainer"]
visible = false
layout_mode = 2
[node name="Info" type="Label" parent="TabContainer/Export"]
layout_mode = 2
size_flags_vertical = 0
text = "The currently edited folder will be exported into the selected file.
Rows hidden by the filter will NOT be exported, and order follows the current sorting key. Rows on non-selected pages will not be removed.
Hidden columns will NOT be exported."
autowrap_mode = 2
[node name="HSeparator" type="HSeparator" parent="TabContainer/Export"]
layout_mode = 2
[node name="HBoxContainer2" type="HBoxContainer" parent="TabContainer/Export"]
layout_mode = 2
alignment = 1
[node name="Label2" type="Label" parent="TabContainer/Export/HBoxContainer2"]
layout_mode = 2
size_flags_horizontal = 3
text = "Delimeter:"
[node name="Button" type="Button" parent="TabContainer/Export/HBoxContainer2"]
layout_mode = 2
toggle_mode = true
text = "Comma (,)"
[node name="Button2" type="Button" parent="TabContainer/Export/HBoxContainer2"]
layout_mode = 2
toggle_mode = true
text = "Semicolon (;)"
[node name="Button3" type="Button" parent="TabContainer/Export/HBoxContainer2"]
layout_mode = 2
toggle_mode = true
text = "Tab"
[node name="CheckBox" type="CheckBox" parent="TabContainer/Export/HBoxContainer2"]
layout_mode = 2
text = "With space after"
[node name="HBoxContainer3" type="HBoxContainer" parent="TabContainer/Export"]
layout_mode = 2
[node name="CheckBox" type="CheckBox" parent="TabContainer/Export/HBoxContainer3"]
layout_mode = 2
text = "First row contains property names (CSV)"
[node name="StyleSettingsE" parent="TabContainer/Export" instance=ExtResource("4")]
layout_mode = 2
[node name="Control" type="Control" parent="TabContainer/Export"]
layout_mode = 2
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Export"]
layout_mode = 2
alignment = 1
[node name="Button" type="Button" parent="TabContainer/Export/HBoxContainer"]
layout_mode = 2
text = "Export to CSV"
[node name="Cancel" type="Button" parent="TabContainer/Export/HBoxContainer"]
layout_mode = 2
text = "Cancel"
[node name="Control2" type="Control" parent="TabContainer/Export"]
layout_mode = 2
[connection signal="pressed" from="TabContainer/Import/HBoxContainer/Cancel" to="." method="hide"]
[connection signal="pressed" from="TabContainer/Export/HBoxContainer/Cancel" to="." method="hide"]

View File

@ -0,0 +1,20 @@
@tool
extends GridContainer
signal format_changed(case, delimiter, bool_yes, bool_no)
func _send_signal(arg1 = null):
format_changed.emit(
$"HBoxContainer/Case".selected,
[" ", "_", "-"][$"HBoxContainer/Separator".selected],
$"HBoxContainer2/True".text,
$"HBoxContainer2/False".text
)
func _on_format_changed(case, delimiter, bool_yes, bool_no):
$"HBoxContainer/Case".selected = case
$"HBoxContainer/Separator".selected = [" ", "_", "-"].find(delimiter)
$"HBoxContainer2/True".text = bool_yes
$"HBoxContainer2/False".text = bool_no

View File

@ -0,0 +1,76 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/resources_speadsheet_view/import_export/import_export_enum_format.gd" type="Script" id=1]
[node name="EnumFormat" type="GridContainer"]
margin_right = 400.0
margin_bottom = 48.0
rect_pivot_offset = Vector2( 40, 71 )
columns = 2
script = ExtResource( 1 )
[node name="Label3" type="Label" parent="."]
margin_top = 3.0
margin_right = 198.0
margin_bottom = 17.0
size_flags_horizontal = 3
text = "Enum word case/separator"
[node name="HBoxContainer" type="HBoxContainer" parent="."]
margin_left = 202.0
margin_right = 400.0
margin_bottom = 20.0
size_flags_horizontal = 3
[node name="Case" type="OptionButton" parent="HBoxContainer"]
margin_right = 129.0
margin_bottom = 20.0
size_flags_horizontal = 3
text = "Caps Every Word"
clip_text = true
items = [ "all lower", null, false, 0, null, "caps Except First", null, false, 1, null, "Caps Every Word", null, false, 2, null, "ALL CAPS", null, false, 3, null ]
selected = 2
[node name="Separator" type="OptionButton" parent="HBoxContainer"]
margin_left = 133.0
margin_right = 198.0
margin_bottom = 20.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
text = "Space \" \""
clip_text = true
items = [ "Space \" \"", null, false, 0, null, "Underscore \"_\"", null, false, 1, null, "Kebab \"-\"", null, false, 2, null ]
selected = 0
[node name="Label4" type="Label" parent="."]
margin_top = 29.0
margin_right = 198.0
margin_bottom = 43.0
size_flags_horizontal = 3
text = "Boolean True/False"
[node name="HBoxContainer2" type="HBoxContainer" parent="."]
margin_left = 202.0
margin_top = 24.0
margin_right = 400.0
margin_bottom = 48.0
size_flags_horizontal = 3
[node name="True" type="LineEdit" parent="HBoxContainer2"]
margin_right = 97.0
margin_bottom = 24.0
size_flags_horizontal = 3
text = "Yes"
[node name="False" type="LineEdit" parent="HBoxContainer2"]
margin_left = 101.0
margin_right = 198.0
margin_bottom = 24.0
size_flags_horizontal = 3
text = "No"
[connection signal="mouse_entered" from="Label3" to="." method="_on_Label3_mouse_entered"]
[connection signal="item_selected" from="HBoxContainer/Case" to="." method="_send_signal"]
[connection signal="item_selected" from="HBoxContainer/Separator" to="." method="_send_signal"]
[connection signal="text_changed" from="HBoxContainer2/True" to="." method="_send_signal"]
[connection signal="text_changed" from="HBoxContainer2/False" to="." method="_send_signal"]

View File

@ -0,0 +1,12 @@
@tool
extends HBoxContainer
func display(name : String, type : int):
$"LineEdit".text = name
$"OptionButton".selected = type
func connect_all_signals(to : Object, index : int, prefix : String = "_on_list_item_"):
$"LineEdit".text_changed.connect(Callable(to, prefix + "name_changed").bind(index))
$"OptionButton".item_selected.connect(Callable(to, prefix + "type_selected").bind(index))

View File

@ -0,0 +1,26 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/resources_speadsheet_view/import_export/property_list_item.gd" type="Script" id=1]
[node name="Entry" type="HBoxContainer"]
margin_right = 468.0
margin_bottom = 24.0
script = ExtResource( 1 )
[node name="LineEdit" type="LineEdit" parent="."]
margin_right = 309.0
margin_bottom = 24.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
text = "1"
[node name="OptionButton" type="OptionButton" parent="."]
margin_left = 313.0
margin_right = 468.0
margin_bottom = 24.0
size_flags_horizontal = 3
size_flags_vertical = 5
size_flags_stretch_ratio = 0.25
text = "Bool"
items = [ "Bool", null, false, 1, null, "Integer Number", null, false, 2, null, "Floating Point Number", null, false, 3, null, "String/Other", null, false, 4, null, "Vector2", null, true, 5, null, "Rect2", null, true, 6, null, "Vector3", null, true, 7, null, "Color", null, false, 14, null, "Array", null, true, 19, null, "Resource Path", null, false, 17, null, "Enumeration", null, false, 101, null ]
selected = 0

View File

@ -0,0 +1,293 @@
@tool
class_name SpreadsheetImport
extends Resource
enum PropType {
BOOL,
INT,
FLOAT,
STRING,
VECTOR2,
RECT2,
VECTOR3,
COLOR,
ARRAY,
OBJECT,
ENUM,
MAX,
}
enum NameCasing {
ALL_LOWER,
CAPS_WORD_EXCEPT_FIRST,
CAPS_WORD,
ALL_CAPS,
}
const SUFFIX := "_spreadsheet_import.tres"
const TYPE_MAP := {
TYPE_STRING : PropType.STRING,
TYPE_FLOAT : PropType.FLOAT,
TYPE_BOOL : PropType.BOOL,
TYPE_INT : PropType.INT,
TYPE_OBJECT : PropType.OBJECT,
TYPE_COLOR : PropType.COLOR,
}
@export var prop_types : Array
@export var prop_names : Array
@export var edited_path := "res://"
@export var prop_used_as_filename := ""
@export var script_classname := ""
@export var remove_first_row := true
@export var new_script : GDScript
@export var view_script : Script = SpreadsheetEditFormatCsv
@export var delimeter := ";"
@export var enum_format : Array = [NameCasing.CAPS_WORD, " ", "Yes", "No"]
@export var uniques : Dictionary
func initialize(path):
edited_path = path
prop_types = []
prop_names = []
func save():
ResourceSaver.call_deferred("save", edited_path.get_basename() + SUFFIX, self)
func string_to_property(string : String, col_index : int):
match prop_types[col_index]:
PropType.STRING:
return string
PropType.BOOL:
string = string.to_lower()
if string == enum_format[2].to_lower(): return true
if string == enum_format[3].to_lower(): return false
return !string in ["no", "disabled", "-", "false", "absent", "wrong", "off", ""]
PropType.FLOAT:
return string.to_float()
PropType.INT:
return string.to_int()
PropType.COLOR:
return Color(string)
PropType.OBJECT:
return load(string)
PropType.ENUM:
if string == "":
return int(uniques[col_index]["N_A"])
else:
return int(uniques[col_index][string.capitalize().replace(" ", "_").to_upper()])
func property_to_string(value, col_index : int) -> String:
if value == null: return ""
if col_index == 0:
if prop_names[col_index] == "resource_path":
return value.get_file().get_basename()
if prop_types[col_index] is PackedStringArray:
return prop_types[col_index][value].capitalize()
match prop_types[col_index]:
PropType.STRING:
return str(value)
PropType.BOOL:
return enum_format[2] if value else enum_format[3]
PropType.FLOAT, PropType.INT:
return str(value)
PropType.COLOR:
return value.to_html()
PropType.OBJECT:
return value.resource_path
PropType.ENUM:
var dict = uniques[col_index]
for k in dict:
if dict[k] == value:
return change_name_to_format(k, enum_format[0], enum_format[1])
return str(value)
func create_property_line_for_prop(col_index : int) -> String:
var result = "@export var " + prop_names[col_index] + " :"
match prop_types[col_index]:
PropType.STRING:
return result + "= \"\"\r\n"
PropType.BOOL:
return result + "= false\r\n"
PropType.FLOAT:
return result + "= 0.0\r\n"
PropType.INT:
return result + "= 0\r\n"
PropType.COLOR:
return result + "= Color.white\r\n"
PropType.OBJECT:
return result + " Resource\r\n"
PropType.ENUM:
return result + ": %s\r\n" % _escape_forbidden_enum_names(prop_names[col_index].capitalize().replace(" ", ""))
# return result.replace(
# "@export var",
# "@export_enum(" + _escape_forbidden_enum_names(
# prop_names[col_index].capitalize()\
# .replace(" ", "")
# ) + ") var"
# ) + "= 0\r\n"
return ""
func _escape_forbidden_enum_names(string : String) -> String:
if ClassDB.class_exists(string):
return string + "_"
# Not in ClassDB, but are engine types and can be property names
if string in [
"Color", "String", "Plane",
"Basis", "Transform", "Variant",
]:
return string + "_"
return string
func create_enum_for_prop(col_index) -> String:
var result := (
"enum "
+ _escape_forbidden_enum_names(
prop_names[col_index].capitalize().replace(" ", "")
) + " {\r\n"
)
for k in uniques[col_index]:
result += (
"\t"
+ k # Enum Entry
+ " = "
+ str(uniques[col_index][k]) # Value
+ ",\r\n"
)
result += "\tMAX,\r\n}\r\n\r\n"
return result
func generate_script(entries, has_classname = true) -> GDScript:
var source = ""
if has_classname and script_classname != "":
source = "class_name " + script_classname + " \r\nextends Resource\r\n\r\n"
else:
source = "extends Resource\r\n\r\n"
# Enums
uniques = get_uniques(entries)
for i in prop_types.size():
if prop_types[i] == PropType.ENUM:
source += create_enum_for_prop(i)
# Properties
for i in prop_names.size():
if (prop_names[i] != "resource_path") and (prop_names[i] != "resource_name"):
source += create_property_line_for_prop(i)
var created_script = GDScript.new()
created_script.source_code = source
created_script.reload()
return created_script
func strings_to_resource(strings : Array):
var new_res = new_script.new()
for j in min(prop_names.size(), strings.size()):
new_res.set(prop_names[j], string_to_property(strings[j], j))
if prop_used_as_filename != "":
new_res.resource_path = edited_path.get_basename() + "/" + new_res.get(prop_used_as_filename) + ".tres"
return new_res
func resource_to_strings(res : Resource):
var strings := []
strings.resize(prop_names.size())
for i in prop_names.size():
strings[i] = property_to_string(res.get(prop_names[i]), i)
return PackedStringArray(strings)
func get_uniques(entries : Array) -> Dictionary:
var result = {}
for i in prop_types.size():
if prop_types[i] == PropType.ENUM:
var cur_value := ""
result[i] = {}
for j in entries.size():
if j == 0 and remove_first_row: continue
cur_value = entries[j][i].capitalize().to_upper().replace(" ", "_")
if cur_value == "":
cur_value = "N_A"
if !result[i].has(cur_value):
result[i][cur_value] = result[i].size()
return result
static func change_name_to_format(name : String, case : int, delim : String):
var string = name.capitalize().replace(" ", delim)
if case == NameCasing.ALL_LOWER:
return string.to_lower()
if case == NameCasing.CAPS_WORD_EXCEPT_FIRST:
return string[0].to_lower() + string.substr(1)
if case == NameCasing.CAPS_WORD:
return string
if case == NameCasing.ALL_CAPS:
return string.to_upper()
static func get_resource_property_types(res : Resource, properties : Array) -> Array:
var result = []
result.resize(properties.size())
result.fill(PropType.STRING)
var cur_type := 0
for x in res.get_property_list():
var found = properties.find(x["name"])
if found == -1: continue
if x["usage"] & PROPERTY_USAGE_EDITOR != 0:
if x["hint"] == PROPERTY_HINT_ENUM:
var enum_values = x["hint_string"].split(",")
for i in enum_values.size():
enum_values[i] = enum_values[i].left(enum_values[i].find(":"))
result[found] = enum_values
else:
result[found] = TYPE_MAP.get(x["type"], PropType.STRING)
return result

View File

@ -5,5 +5,5 @@ description="Edit Many Resources from one Folder as a table.
Heavily inspired by Multi-Cursor-Editing in text editors, so after selecting multiple cells (in the same column!) using Ctrl+Click or Shift+Click, most Basic-to-Intermediate movements should be available."
author="Don Tnowe"
version="2.1"
version="2.3"
script="plugin.gd"

View File

@ -1 +0,0 @@
{ "recent_paths": ["res://example/Random Upgrades/upgrades", "res://example/Random Upgrades/upgrades/"], "hidden_columns": { "res://example/Random Upgrades/upgrades/": { "resource_name": true, "resource_local_to_scene": true, "list_item_delimeter": true, "list_row_delimeter": true } } }

View File

@ -0,0 +1,177 @@
@tool
extends MarginContainer
enum {
EDITBOX_DUPLICATE = 1,
EDITBOX_RENAME,
EDITBOX_DELETE,
}
@export var editor_view := NodePath("../..")
@onready var editbox_node := $"Control/ColorRect/Popup"
@onready var editbox_label := editbox_node.get_node("Panel/VBoxContainer/Label")
@onready var editbox_input := 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")
)
func _on_grid_cells_context(cells):
open(cells)
func _on_grid_cells_selected(cells):
if ProjectSettings.get_setting(SettingsGrid.SETTING_PREFIX + "context_menu_on_leftclick"):
open(cells, true)
else: hide()
func open(cells : Array, pin_to_cell : bool = false):
if cells.size() == 0:
hide()
cell = null
return
if pin_to_cell:
cell = cells[-1]
global_position = Vector2(
cell.global_position.x + cell.size.x,
cell.global_position.y
)
else:
cell = null
global_position = get_global_mouse_position() + Vector2.ONE
size = Vector2.ZERO
top_level = true
show()
$"Control2/Label".text = str(cells.size()) + (" Cells" if cells.size() % 10 != 1 else " Cell")
$"GridContainer/Rename".visible = get_node(editor_view).has_row_names()
func _unhandled_input(event):
if !get_node(editor_view).is_visible_in_tree():
hide()
return
if event is InputEventKey:
if Input.is_key_pressed(KEY_CTRL):
# Dupe
if event.keycode == KEY_D:
_on_Duplicate_pressed()
return
# Rename
if event.keycode == KEY_R:
_on_Rename_pressed()
return
if event is InputEventMouseButton && event.is_pressed():
hide()
func _input(event):
if cell == null: return
if !get_node(editor_view).is_visible_in_tree():
hide()
return
global_position = Vector2(
cell.global_position.x + cell.size.x,
cell.global_position.y
)
func _on_Duplicate_pressed():
_show_editbox(EDITBOX_DUPLICATE)
func _on_CbCopy_pressed():
TextEditingUtils.multi_copy(get_node(editor_view).edited_cells_text)
func _on_CbPaste_pressed():
get_node(editor_view).set_edited_cells_values(
TextEditingUtils.multi_paste(
get_node(editor_view).edited_cells_text,
get_node(editor_view).edit_cursor_positions
)
)
func _on_Rename_pressed():
_show_editbox(EDITBOX_RENAME)
func _on_Delete_pressed():
_show_editbox(EDITBOX_DELETE)
func _show_editbox(action):
var node_editor_view = get_node(editor_view)
editbox_action = action
match action:
EDITBOX_DUPLICATE:
if !node_editor_view.has_row_names():
_on_editbox_accepted()
return
if node_editor_view.edited_cells.size() == 1:
editbox_label.text = "Input new row's name..."
editbox_input.text = node_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 = node_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 = node_editor_view.get_last_selected_row()\
.resource_path.get_file().get_basename()
editbox_input.grab_focus()
editbox_input.caret_position = 999999999
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:
get_node(editor_view).duplicate_selected_rows(editbox_input.text)
EDITBOX_RENAME:
get_node(editor_view).rename_row(get_node(editor_view).get_last_selected_row(), editbox_input.text)
EDITBOX_DELETE:
get_node(editor_view).delete_selected_rows()
_on_editbox_closed()

View File

@ -0,0 +1,217 @@
[gd_scene load_steps=8 format=2]
[ext_resource path="res://addons/resources_speadsheet_view/editor_icon_button.gd" type="Script" id=1]
[ext_resource path="res://addons/resources_speadsheet_view/selection_actions.gd" type="Script" id=2]
[sub_resource type="Image" id=6]
data = {
"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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]
flags = 4
flags = 4
image = SubResource( 6 )
size = Vector2( 16, 16 )
[sub_resource type="Image" id=7]
data = {
"data": PoolByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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=4]
flags = 4
flags = 4
image = SubResource( 7 )
size = Vector2( 16, 16 )
[sub_resource type="StyleBoxTexture" id=5]
texture = SubResource( 4 )
region_rect = Rect2( 0, 0, 16, 16 )
margin_left = 2.0
margin_right = 2.0
margin_top = 2.0
margin_bottom = 2.0
[node name="SelectionActions" type="MarginContainer"]
margin_right = 84.0
margin_bottom = 44.0
mouse_filter = 2
size_flags_horizontal = 9
script = ExtResource( 2 )
__meta__ = {
"_edit_group_": true,
"_edit_lock_": true
}
[node name="Control2" type="Control" parent="."]
margin_right = 84.0
margin_bottom = 44.0
mouse_filter = 2
[node name="ColorRect" type="ColorRect" parent="Control2"]
show_behind_parent = true
margin_left = -2.0
margin_top = -18.0
margin_bottom = 2.0
mouse_filter = 2
[node name="ColorRect2" type="ColorRect" parent="Control2"]
show_behind_parent = true
anchor_bottom = 1.0
margin_left = -2.0
mouse_filter = 2
[node name="Label" type="Label" parent="Control2"]
margin_left = 2.0
margin_top = -16.0
margin_right = 50.0
margin_bottom = -2.0
text = "Actions"
[node name="ColorRect3" type="Panel" parent="Control2/Label"]
show_behind_parent = true
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = -2.0
margin_top = -2.0
margin_bottom = 2.0
mouse_filter = 2
[node name="GridContainer" type="GridContainer" parent="."]
margin_right = 84.0
margin_bottom = 44.0
mouse_filter = 2
size_flags_horizontal = 9
size_flags_vertical = 9
custom_constants/vseparation = 0
custom_constants/hseparation = 0
columns = 3
[node name="Duplicate" type="Button" parent="GridContainer"]
margin_right = 28.0
margin_bottom = 22.0
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"]
margin_left = 28.0
margin_right = 56.0
margin_bottom = 22.0
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"]
margin_left = 56.0
margin_right = 84.0
margin_bottom = 22.0
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"]
margin_top = 22.0
margin_right = 28.0
margin_bottom = 44.0
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"]
margin_left = 28.0
margin_top = 22.0
margin_right = 56.0
margin_bottom = 44.0
tooltip_text = "DELETE Selected Rows"
mouse_filter = 1
icon = SubResource( 2 )
script = ExtResource( 1 )
icon_name = "Remove"
[node name="Control" type="Control" parent="."]
margin_right = 84.0
margin_bottom = 44.0
mouse_filter = 2
[node name="ColorRect" type="ColorRect" parent="Control"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
color = Color( 0, 0, 0, 0.498039 )
[node name="Popup" type="Popup" parent="Control/ColorRect"]
visible = true
margin_left = 42.0
margin_top = 22.0
margin_right = 42.0
margin_bottom = 22.0
mouse_filter = 2
popup_exclusive = true
[node name="Panel" type="PanelContainer" parent="Control/ColorRect/Popup"]
margin_right = 14.0
margin_bottom = 14.0
rect_min_size = Vector2( 192, 0 )
mouse_filter = 2
custom_styles/panel = SubResource( 5 )
[node name="VBoxContainer" type="VBoxContainer" parent="Control/ColorRect/Popup/Panel"]
margin_left = 7.0
margin_top = 7.0
margin_right = 185.0
margin_bottom = 73.0
mouse_filter = 2
[node name="Label" type="Label" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
margin_right = 178.0
margin_bottom = 14.0
text = "Input new name..."
[node name="LineEdit" type="LineEdit" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
margin_top = 18.0
margin_right = 178.0
margin_bottom = 42.0
[node name="HBoxContainer" type="HBoxContainer" parent="Control/ColorRect/Popup/Panel/VBoxContainer"]
margin_top = 46.0
margin_right = 178.0
margin_bottom = 66.0
alignment = 1
[node name="Button" type="Button" parent="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer"]
margin_left = 44.0
margin_right = 75.0
margin_bottom = 20.0
text = "OK"
[node name="Button2" type="Button" parent="Control/ColorRect/Popup/Panel/VBoxContainer/HBoxContainer"]
margin_left = 79.0
margin_right = 133.0
margin_bottom = 20.0
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="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

@ -7,7 +7,7 @@ const SETTING_PREFIX = "addons/resources_spreadsheet_view/"
func _ready():
for x in get_children():
var setting = SETTING_PREFIX + TextEditingUtils.pascal_case_to_snake_case(x.name)
var setting = SETTING_PREFIX + x.name.to_snake_case()
if x is BaseButton:
x.toggled.connect(_set_setting.bind(setting))
if !ProjectSettings.has_setting(setting):

View File

@ -5,7 +5,7 @@ var editor_view : Control
func set_label(label : String):
$"Button".text = TextEditingUtils.string_snake_to_naming_case(label)
$"Button".text = label.capitalize()
$"Button".tooltip_text = label + "\nClick to sort."

View File

@ -1,33 +1,16 @@
[gd_scene load_steps=5 format=3 uid="uid://bya5ugi40ptxl"]
[gd_scene load_steps=3 format=3 uid="uid://d1s6oihqedvo5"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/table_header.gd" id="1_xhap0"]
[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_t08bs"]
[sub_resource type="Image" id="Image_1jisk"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_wgkau"]
image = SubResource("Image_1jisk")
[node name="Header" type="HBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
offset_right = 179.0
offset_bottom = 31.0
size_flags_horizontal = 3
script = ExtResource("1_xhap0")
[node name="Button" type="Button" parent="."]
layout_mode = 2
offset_right = 1124.0
offset_bottom = 31.0
size_flags_horizontal = 3
size_flags_vertical = 0
text = "resource_name"
@ -35,11 +18,7 @@ clip_text = true
[node name="Button2" type="MenuButton" parent="."]
layout_mode = 2
offset_left = 1128.0
offset_right = 1152.0
offset_bottom = 648.0
size_flags_horizontal = 9
icon = SubResource("ImageTexture_wgkau")
item_count = 2
popup/item_0/text = "Select All"
popup/item_0/id = 0

View File

@ -3,18 +3,10 @@ extends HBoxContainer
@export @onready var node_editor_view_root : Control = $"../../../.."
# These can not be set externally.
var rows_per_page := 50:
set(v): pass
var current_page := 0:
set(v): pass
var first_row := 0:
set(v): pass
var last_row := 50:
set(v): pass
var rows_per_page := 50
var current_page := 0
var first_row := 0
var last_row := 50
func _on_grid_updated():
@ -85,7 +77,7 @@ func _fill_buttons_with_prefixes(btns, strings, page_count):
continue
for j in strings[i].length():
if strings[i].ord_at(j) != strings[i - 1].ord_at(j):
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

View File

@ -89,7 +89,7 @@ static func multi_move_right(values : Array, cursor_positions : Array, ctrl_pres
static func multi_paste(values : Array, cursor_positions : Array):
var pasted_lines := DisplayServer.clipboard_get().split("\n")
var pasted_lines := DisplayServer.clipboard_get().replace("\r", "").split("\n")
var paste_each_line := pasted_lines.size() == values.size()
for i in values.size():
@ -140,25 +140,3 @@ static func _step_cursor(text : String, start : int, step : int = 1, ctrl_presse
return start
return 0
static func string_snake_to_naming_case(string : String, add_spaces : bool = true) -> String:
if string == "": return ""
var split = string.split("_")
for i in split.size():
split[i] = split[i][0].to_upper() + split[i].substr(1).to_lower()
return (" " if add_spaces else "").join(split)
static func pascal_case_to_snake_case(string : String) -> String:
var i = 0
while i < string.length():
if string.unicode_at(i) < 97:
string = string.left(i) + ("_" if i > 0 else "") + string[i].to_lower() + string.substr(i + 1)
i += 1
i += 1
return string

View File

@ -31,7 +31,7 @@ func _on_Button_pressed():
tile.region = Rect2(tile_size * Vector2(i, j), tile_size)
tile.atlas = _stored_value
tile_array.append(tile)
tile.take_over_path(folder_name + "/" + folder_name.get_file() + "_" + str(j * v_count + i + 1) + ".tres")
tile.take_over_path(folder_name + "/" + folder_name.get_file() + "_" + str(j * h_count + i + 1) + ".tres")
ResourceSaver.save(tile)
tile_array.resize(sheet.edited_cells.size())

View File

@ -8,57 +8,6 @@
config_version=5
_global_script_classes=[{
"base": "RefCounted",
"class": &"CellEditor",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/typed_cells/cell_editor.gd"
}, {
"base": "CellEditor",
"class": &"CellEditorArray",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd"
}, {
"base": "GridContainer",
"class": &"SettingsGrid",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/settings_grid.gd"
}, {
"base": "Control",
"class": &"SheetsDockEditor",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/typed_editors/dock_base.gd"
}, {
"base": "RefCounted",
"class": &"TextEditingUtils",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/text_editing_utils.gd"
}, {
"base": "Control",
"class": &"ThemeColorSetter",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/editor_color_setter.gd"
}, {
"base": "Button",
"class": &"ThemeIconButton",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/editor_icon_button.gd"
}, {
"base": "Control",
"class": &"ThemeStylebox",
"language": &"GDScript",
"path": "res://addons/resources_spreadsheet_view/editor_stylebox_overrider.gd"
}]
_global_script_class_icons={
"CellEditor": "",
"CellEditorArray": "",
"SettingsGrid": "",
"SheetsDockEditor": "",
"TextEditingUtils": "",
"ThemeColorSetter": "",
"ThemeIconButton": "",
"ThemeStylebox": ""
}
color_rows=false
color_arrays=true
@ -76,6 +25,10 @@ resources_spreadsheet_view/resource_preview_size=32.0
config/name="Addon: Resources as Sheets"
config/features=PackedStringArray("4.0")
[editor]
export/convert_text_resources_to_binary=true
[editor_plugins]
enabled=PackedStringArray("res://addons/resources_spreadsheet_view/plugin.cfg")