移除godot表插件

This commit is contained in:
小李xl 2023-06-03 00:15:08 +08:00
parent 28f6cd4b85
commit abba859cbf
59 changed files with 2 additions and 5426 deletions

3
.gitignore vendored
View File

@ -13,4 +13,5 @@
/DScript/DScript_Compiler_Test/obj
/DScript/DScript_Runtime/Backups
/DScript/DScript.sln.DotSettings.user
**/.idea
**/.idea
**/~$*

Binary file not shown.

View File

@ -1,11 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.1.0-dev.1">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
<ItemGroup>
<Folder Include="src\game\ui\editorTools" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.1.0-dev.2">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
<ItemGroup>
<Folder Include="src\game\ui\editorTools" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.1.0-dev.1">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
<ItemGroup>
<Folder Include="src\game\ui\editorTools" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.1.0-dev.3">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
<ItemGroup>
<Folder Include="src\game\ui\editorTools" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.1.0-dev.1">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
<ItemGroup>
<Folder Include="src\game\ui\editorTools" />
</ItemGroup>
</Project>

View File

@ -1,11 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.1.0-dev.3">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
</PropertyGroup>
<ItemGroup>
<Folder Include="src\game\ui\editorTools" />
</ItemGroup>
</Project>

View File

@ -1,310 +0,0 @@
#============================================================
# Data Util
#============================================================
# - datetime: 2022-12-21 21:19:10
#============================================================
## 数据工具
##
##用作全局获取数据使用
class_name ScriptCommentMenu_DataUtil
## 获取场景树 [SceneTree] 对象的 meta 数据作为单例数据,如果返回的数据为 [code]null[/code] 则会在下次继续调用这个
##default 回调方法,直到返回的数据不为 [code]null[/code] 为止
##[br]
##[br][code]meta_key[/code] 数据key
##[br][code]default[/code] 如果没有这个key则默认返回的数据
##[br][code]ignore_null[/code] 忽略 null 值。如果为 true则在默认值为 null 的时候不记录到元数据,直到有数据为止
static func get_meta_data(meta_key: StringName, default: Callable, ignore_null: bool = true):
if Engine.has_meta(meta_key) and Engine.get_meta(meta_key) != null:
return Engine.get_meta(meta_key)
else:
var value = default.call()
if ignore_null:
if value != null:
set_meta_data(meta_key, value)
else:
set_meta_data(meta_key, value)
return value
## 设置数据
##[br]
##[br][code]meta_key[/code] 数据key
##[br][code]value[/code] 设置的值
static func set_meta_data(meta_key: StringName, value):
Engine.set_meta(meta_key, value)
## 是否有这个 key 的据
static func has_meta_data(meta_key: StringName) -> bool:
return Engine.has_meta(meta_key)
## 移除数据
static func remove_meta_data(meta_key: StringName) -> bool:
if Engine.has_meta(meta_key):
Engine.remove_meta(meta_key)
return true
return false
## 移除所有meta数据
static func clear_all_meta() -> void:
for key in Engine.get_meta_list():
Engine.remove_meta(key)
## 获取 Dictionary 数据
static func get_meta_dict_data(meta_key: StringName, default: Dictionary = {}) -> Dictionary:
if Engine.has_meta(meta_key):
return Engine.get_meta(meta_key)
else:
Engine.set_meta(meta_key, default)
return default
## 获取 Array 数据
static func get_meta_array_data(meta_key: StringName, default: Array = []) -> Array:
if Engine.has_meta(meta_key):
return Engine.get_meta(meta_key)
else:
Engine.set_meta(meta_key, default)
return default
## 获取目标的默认数据,以目标对象作为基础存储数据
static func get_object_data(object: Object, key: StringName, default: Callable ):
if object.has_meta(key):
return object.get_meta(key)
else:
var data = default.call()
object.set_meta(key, data)
return data
## 获取标 [Dictionary] 类型数据
static func get_object_dict_data(object: Object, key: StringName, default: Dictionary = {}) -> Dictionary:
return get_object_data(object, key, func(): return default)
class _ClassInfo:
var _type : int = TYPE_NIL
var _class_name : StringName = &""
var _script : Script = null
func _to_string():
return str({
"_type": _type,
"_class_name": _class_name,
"_script": _script,
})
## 获取类的数据
##[br]
##[br][code]_class[/code] 类型。这个值可以是类名称,也可以是 [int] 类的数据型枚举的值。最大
## [constant TYPE_MAX],最小 [constant TYPE_NIL]
##[br][code]return[/code] 返回这个类的信息
static func get_class_info(_class) -> _ClassInfo:
var map = get_meta_dict_data("DataUtil_get_type_cache_data_for_array", {})
if map.has(_class):
return map[_class] as _ClassInfo
else:
var type : int = TYPE_NIL
var _class_name : StringName = &""
var script = null
if _class is Script:
type = TYPE_OBJECT
_class_name = _class.get_instance_base_type()
script = _class
elif _class is int and _class > 0 and _class < TYPE_MAX:
type = _class
_class = ScriptCommentMenu_ScriptUtil.get_type_name(_class)
elif _class is Object:
var _class_type_ = str(_class)
if _class_type_.contains("GDScriptNativeClass"):
var obj = _class.new()
type = typeof(obj)
_class_name = obj.get_class()
else:
type = TYPE_OBJECT
_class_name = "Object"
elif _class is String:
if ScriptCommentMenu_ScriptUtil.is_base_data_type(_class):
type = ScriptCommentMenu_ScriptUtil.get_type_of(_class)
_class = ScriptCommentMenu_ScriptUtil.get_built_in_class(_class)
else:
type = TYPE_OBJECT
var data = _ClassInfo.new()
data._type = type
data._class_name = _class_name
data._script = script
map[_class] = data
return data
## 获取类型化数组
##[br]
##[br][code]_class[/code] 数据的类型。比如 [code]"Dictionary", Node, Sprite2D[/code] 等类名(基础数据类型需要加双引号),
##或者自定义类名 Player或者字符串形式的类名或者 TYPE_INT, TYPE_DICTIONARY
##[br][code]default[/code] 默认有哪些数据
static func get_type_array(_class, default : Array = []) -> Array:
var data : _ClassInfo = get_class_info(_class)
# 返回类型化数组
return Array(default, data._type, data._class_name, data._script )
## 转为类型化数组
static func to_type_array(_class, array: Array) -> Array:
return get_type_array(_class, array)
## 数组转为字典
##
##[codeblock]
##var dict_data = ScriptCommentMenu_DataUtil.array_to_dictionary(
## node_list,
## func(node): return node.name, # key 键
## func(node): return {}
##)
##[/codeblock]
static func array_to_dictionary(
list: Array,
get_key: Callable = func(item): return item,
get_value: Callable = func(item): return null
) -> Dictionary:
var data = {}
var key
var value
for i in list:
key = get_key.call(i)
value = get_value.call(i)
data[key] = value
return data
## 引用数据
class RefData:
var value
func get_value():
return value
func _init(value) -> void:
self.value = value
func _to_string():
return str(value)
## 获取引用数据。
##[br]
##[br][b]Note:[/b] 主要用在匿名函数里,以处理基本数据类型的值。因为匿名函数之外的基本数据类型的值
##在匿名函数修改不会发生改变。
static func get_ref_data(default) -> RefData:
return RefData.new(default)
## 获取字典的值,如果没有,则获取并设置默认值
##[br]
##[br][code]dict[/code] 获取的字典
##[br][code]key[/code] key 键
##[br][code]not_exists_set[/code] 没有则返回值设置这个值。这个回调方法返回要设置的数据
static func get_value_or_set(dict: Dictionary, key, not_exists_set: Callable):
if dict.has(key) and not typeof(dict[key]) == TYPE_NIL:
return dict[key]
else:
dict[key] = not_exists_set.call()
return dict[key]
## 生成id
static func generate_id(data_list: Array) -> StringName:
var list = []
for i in data_list:
list.append(hash(i))
return ",".join(list).sha1_text()
## 如果不为空值结果值
class NotNullValueChain:
func _init(value):
set_meta("value", value)
func get_value(default = null):
return get_meta("value", default)
func or_else(object, else_object: Callable) -> NotNullValueChain:
return NotNullValueChain.new( object if object else else_object.call() )
## 返回结果不为空时,这个方法需要一个参数接收值
func if_not_null(else_object: Callable, default = null) -> NotNullValueChain:
var value = get_value()
return NotNullValueChain.new( else_object.call(value) if value else default )
## 如果对象不为 null 则调用。
## 可以链式调用逐步执行功能
##[codeblock]
##func get_data(object: Object):
## return ScriptCommentMenu_DataUtil.if_not_null(object, func():
## return object.get_script()
## ).or_else(func():
## print("")
## )
##[/codeblock]
static func if_not_null(object, else_object: Callable) -> NotNullValueChain:
return NotNullValueChain.new((
else_object.call() if object != null else object
))
## 获取正则
static func get_regex(pattern: String) -> RegEx:
var re = RegEx.new()
re.compile(pattern)
return re
## 合并数据
##[br]
##[br][code]merge_target[/code] 合并到的目标
##[br][code]data[/code] 要追加合并的数据
##[br][return]return[/return] 返回合并后的数据
static func merge(merge_target, data):
if merge_target is Dictionary:
merge_target.merge(data)
return merge_target
elif merge_target is Array or merge_target is String:
merge_target += merge_target
return merge_target
else:
assert(false, "错误的数据类型!只能合并 [Dictionary, Array, String] 中的一种!")
## 获取一个唯一的数字 ID从 0 始
static func get_id() -> int:
const KEY = "DataUtil_get_id"
if Engine.has_meta(KEY):
var id = Engine.get_meta(KEY)
id += 1
Engine.set_meta(KEY, id)
return id
else:
var id = 0
Engine.set_meta(KEY, id)
return id
## 列表转为集合hash值这样即便列表顺序不一致他的值也是相同的
static func as_set_hash(list: Array) -> int:
var h : int = 0
for i in list:
h += hash(i)
return h

View File

@ -1,7 +0,0 @@
[plugin]
name="script_comment_menu"
description=""
author="张学徒"
version="1.2"
script="plugin.gd"

View File

@ -1,47 +0,0 @@
#============================================================
# Plugin
#============================================================
# - datetime: 2022-06-11 11:26:00
# - datetime: 2022-07-17 14:54:39
#============================================================
@tool
extends EditorPlugin
var menu_button : MenuButton
var util_add_menu := ScriptCommentMenuConstant.AddMenu.new()
var _sub_menus = [
_ScriptMenu_Comments.new(),
_ScriptMenu_Overrides.new(),
]
func _enter_tree():
# 编辑器启动不超过 5 秒时
if Time.get_ticks_msec() < 5000:
await Engine.get_main_loop().create_timer(10).timeout
_init_data.call_deferred()
func _exit_tree():
if menu_button:
menu_button.queue_free()
for sub in _sub_menus:
sub._uninstall()
func _init_data():
# 添加菜单按钮
menu_button = MenuButton.new()
menu_button.text = "代码工具"
menu_button.switch_on_hover = true
menu_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
util_add_menu.add_script_editor_menu(menu_button)
for sub in _sub_menus:
sub.init_menu(menu_button)

View File

@ -1,401 +0,0 @@
#============================================================
# Scirpt Util
#============================================================
# - datetime: 2022-07-17 17:25:00
#============================================================
## 处理脚本的工具
class_name ScriptCommentMenu_ScriptUtil
const DATA_TYPE_TO_NAME = {
TYPE_NIL: &"null",
TYPE_BOOL: &"bool",
TYPE_INT: &"int",
TYPE_FLOAT: &"float",
TYPE_STRING: &"String",
TYPE_RECT2: &"Rect2",
TYPE_VECTOR2: &"Vector2",
TYPE_VECTOR2I: &"Vector2i",
TYPE_VECTOR3: &"Vector3",
TYPE_VECTOR3I: &"Vector3i",
TYPE_TRANSFORM2D: &"Transform2D",
TYPE_VECTOR4: &"Vector4",
TYPE_VECTOR4I: &"Vector4i",
TYPE_PLANE: &"Plane",
TYPE_QUATERNION: &"Quaternion",
TYPE_AABB: &"AABB",
TYPE_BASIS: &"Basis",
TYPE_TRANSFORM3D: &"Transform3D",
TYPE_PROJECTION: &"Projection",
TYPE_COLOR: &"Color",
TYPE_STRING_NAME: &"StringName",
TYPE_NODE_PATH: &"NodePath",
TYPE_RID: &"RID",
TYPE_OBJECT: &"Object",
TYPE_CALLABLE: &"Callable",
TYPE_SIGNAL: &"Signal",
TYPE_DICTIONARY: &"Dictionary",
TYPE_ARRAY: &"Array",
TYPE_PACKED_BYTE_ARRAY: &"PackedByteArray",
TYPE_PACKED_INT32_ARRAY: &"PackedInt32Array",
TYPE_PACKED_INT64_ARRAY: &"PackedInt64Array",
TYPE_PACKED_STRING_ARRAY: &"PackedStringArray",
TYPE_PACKED_VECTOR2_ARRAY: &"PackedVector2Array",
TYPE_PACKED_VECTOR3_ARRAY: &"PackedVector3Array",
TYPE_PACKED_FLOAT32_ARRAY: &"PackedFloat32Array",
TYPE_PACKED_FLOAT64_ARRAY: &"PackedFloat64Array",
TYPE_PACKED_COLOR_ARRAY: &"PackedColorArray",
}
const NAME_TO_DATA_TYPE = {
&"null": TYPE_NIL,
&"bool": TYPE_BOOL,
&"int": TYPE_INT,
&"float": TYPE_FLOAT,
&"String": TYPE_STRING,
&"Rect2": TYPE_RECT2,
&"Vector2": TYPE_VECTOR2,
&"Vector2i": TYPE_VECTOR2I,
&"Vector3": TYPE_VECTOR3,
&"Vector3i": TYPE_VECTOR3I,
&"Transform2D": TYPE_TRANSFORM2D,
&"Vector4": TYPE_VECTOR4,
&"Vector4i": TYPE_VECTOR4I,
&"Plane": TYPE_PLANE,
&"Quaternion": TYPE_QUATERNION,
&"AABB": TYPE_AABB,
&"Basis": TYPE_BASIS,
&"Transform3D": TYPE_TRANSFORM3D,
&"Projection": TYPE_PROJECTION,
&"Color": TYPE_COLOR,
&"StringName": TYPE_STRING_NAME,
&"NodePath": TYPE_NODE_PATH,
&"RID": TYPE_RID,
&"Object": TYPE_OBJECT,
&"Callable": TYPE_CALLABLE,
&"Signal": TYPE_SIGNAL,
&"Dictionary": TYPE_DICTIONARY,
&"Array": TYPE_ARRAY,
&"PackedByteArray": TYPE_PACKED_BYTE_ARRAY,
&"PackedInt32Array": TYPE_PACKED_INT32_ARRAY,
&"PackedInt64Array": TYPE_PACKED_INT64_ARRAY,
&"PackedStringArray": TYPE_PACKED_STRING_ARRAY,
&"PackedVector2Array": TYPE_PACKED_VECTOR2_ARRAY,
&"PackedVector3Array": TYPE_PACKED_VECTOR3_ARRAY,
&"PackedFloat32Array": TYPE_PACKED_FLOAT32_ARRAY,
&"PackedFloat64Array": TYPE_PACKED_FLOAT64_ARRAY,
&"PackedColorArray": TYPE_PACKED_COLOR_ARRAY,
}
static func _get_script_data_cache(script: Script) -> Dictionary:
return ScriptCommentMenu_DataUtil.get_meta_dict_data("ScriptCommentMenu_ScriptUtil__get_script_data_cache")
## 数据类型名称
##[br][code]type[/code]: 数据类型枚举值
##[br][code]return[/code]: 返回数据类型的字符串
static func get_type_name(type: int) -> StringName:
return DATA_TYPE_TO_NAME.get(type)
## 获取这个类名的类型
static func get_type_of(_class_name: StringName) -> int:
return NAME_TO_DATA_TYPE.get(_class_name, -1)
## 是否有这个类型的枚举
static func has_type(type: int) -> bool:
return DATA_TYPE_TO_NAME.has(type)
## 是否是基础数据类型
static func is_base_data_type(_class_name: StringName) -> bool:
return NAME_TO_DATA_TYPE.has(_class_name)
## 获取属性列表
##[br]
##[br]返回类似如下格式的数据
##[codeblock]
##{
## "name": "RefCounted",
## "class_name": &"",
## "type": 0,
## "hint": 0,
## "hint_string": "",
## "usage": 128
##}
##[/codeblock]
static func get_property_data_list(script: Script) -> Array[Dictionary]:
if is_instance_valid(script):
return script.get_script_property_list()
return Array([], TYPE_DICTIONARY, "Dictionary", null)
## 获取方法列表
static func get_method_data_list(script: Script) -> Array[Dictionary]:
if is_instance_valid(script):
return script.get_script_method_list()
ScriptCommentMenu_DataUtil.get_type_array("int")
return Array([], TYPE_DICTIONARY, "Dictionary", null)
## 获取方法的参数列表数据
static func get_method_arguments_list(script: Script, method_name: StringName) -> Array[Dictionary]:
var data = get_method_data(script, method_name)
if data:
return data.get("args", ScriptCommentMenu_DataUtil.get_type_array("Dictionary"))
return ScriptCommentMenu_DataUtil.get_type_array("Dictionary")
## 获取信号列表
static func get_signal_data_list(script: Script) -> Array[Dictionary]:
if is_instance_valid(script):
return script.get_script_signal_list()
return Array([], TYPE_DICTIONARY, "Dictionary", null)
## 获取这个属性名称数据
static func get_property_data(script: Script, property: StringName) -> Dictionary:
var data = _get_script_data_cache(script)
var p_cache_data : Dictionary = ScriptCommentMenu_DataUtil.get_value_or_set(data, "propery_data_cache", func():
var property_data : Dictionary = {}
for i in script.get_script_property_list():
property_data[i['name']] = i
return property_data
)
return p_cache_data.get(property, {})
## 获取这个名称的方法的数据
static func get_method_data(script: Script, method_name: StringName) -> Dictionary:
var data = _get_script_data_cache(script)
var m_cache_data : Dictionary = ScriptCommentMenu_DataUtil.get_value_or_set(data, "method_data_cache", func():
var method_data : Dictionary = {}
for i in script.get_script_method_list():
method_data[i['name']]=i
return method_data
)
return m_cache_data.get(method_name, {})
## 获取这个名称的信号的数据
static func get_signal_data(script: Script, signal_name: StringName):
var data = _get_script_data_cache(script)
var s_cache_data : Dictionary = ScriptCommentMenu_DataUtil.get_value_or_set(data, "script_data_cache", func():
var signal_data : Dictionary = {}
for i in script.get_script_signal_list():
signal_data[i['name']]=i
return signal_data
)
return s_cache_data.get(signal_name, {})
## 获取方法数据
## [br]
## [br][code]script[/code] 脚本
## [br][code]method[/code] 要获取的方法数据的方法名
## [br]
## [br][code]return[/code] 返回脚本的数据信息。
## 包括的 key 有 [code]name[/code], [code]args[/code], [code]default_args[/code]
## , [code]flags[/code], [code]return[/code], [code]id[/code]
func find_method_data(script: Script, method: String) -> Dictionary:
var method_data = script.get_script_method_list()
for m in method_data:
if m['name'] == method:
return m
return {}
## 获取扩展脚本链(扩展的所有脚本)
##[br]
##[br][code]script[/code] Object 对象或脚本
##[br][code]return[/code] 返回继承的脚本路径列表
static func get_extends_link(script: Script) -> PackedStringArray:
var list := PackedStringArray()
while script:
if FileAccess.file_exists(script.resource_path):
list.push_back(script.resource_path)
script = script.get_base_script()
return list
## 获取基础类型继承链类列表
##[br]
##[br][code]_class[/code] 基础类型类名
##[br][code]return[/code] 返回基础的类名列表
static func get_extends_link_base(_class) -> PackedStringArray:
if _class is Script:
_class = _class.get_instance_base_type()
elif _class is Object:
_class = _class.get_class()
var c = _class
var list = []
while c != "":
list.append(c)
c = ClassDB.get_parent_class(c)
return PackedStringArray(list)
## 生成方法代码
##[br]
##[br][code]method_data[/code] 方法数据
##[br][code]return[/code] 返回生成的代码
static func generate_method_code(method_data: Dictionary) -> String:
var temp := method_data.duplicate(true)
var args := ""
for i in temp['args']:
var arg_name = i['name']
var arg_type = ( get_type_name(i['type']) if i['type'] != TYPE_NIL else "")
if arg_type.strip_edges() == "":
arg_type = str(i['class_name'])
if arg_type.strip_edges() != "":
arg_type = ": " + arg_type
args += "%s%s, " % [arg_name, arg_type]
temp['args'] = args.trim_suffix(", ")
if temp['return']['type'] != TYPE_NIL:
temp['return_type'] = get_type_name(temp['return']['type'])
if temp.has('return_type') and temp['return_type'] != "":
temp['return_type'] = " -> " + str(temp['return_type'])
temp['return_sentence'] = "pass\n\treturn super." + temp['name'] + "()"
else:
temp['return_type'] = ""
temp['return_sentence'] = "pass"
return "func {name}({args}){return_type}:\n\t{return_sentence}\n".format(temp)
## 获取对象的脚本
static func get_object_script(object: Object) -> Script:
if object == null:
return null
if object is Script:
return object
return object.get_script() as Script
## 对象是否是 tool 状态
##[br]
##[br][code]object[/code] 返回这个对象的脚本是否是开启 tool 的状态
static func is_tool(object: Object) -> bool:
var script = get_object_script(object)
return script.is_tool() if script else false
## 获取对象的脚本路径,如果不存在脚本,则返回空的字符串
static func get_object_script_path(object: Object) -> String:
var script = get_object_script(object)
return script.resource_path if script else ""
## 获取这个对象的这个方法的信息
##[br]
##[br][code]object[/code] 对象
##[br][code]method_name[/code] 方法名
##[br][code]return[/code] 返回方法的信息
static func get_object_method_data(object: Object, method_name: StringName) -> Dictionary:
if not is_instance_valid(object):
return {}
var script = get_object_script(object)
if script:
return get_method_data(script, method_name)
return {}
## 获取这个信号的数据
static func get_object_signal_data(object: Object, signal_name: StringName) -> Dictionary:
if not is_instance_valid(object):
return {}
var script = get_object_script(object)
if script:
return get_signal_data(script, signal_name)
return {}
## 获取对象的属性数据
static func get_object_property_data(object: Object, proprety_name: StringName) -> Dictionary:
if not is_instance_valid(object):
return {}
var script = get_object_script(object)
if script:
return get_property_data(script, proprety_name)
return {}
## 获取内置类名称转为对象。比如将 "Node" 字符串转为 [Node] 这种 GDScriptNativeClass 类型数据
##[br]
##[br][code]_class[/code] 类名称
static func get_built_in_class (_class: StringName):
if not ClassDB.class_exists(_class):
return null
var _class_db = ScriptCommentMenu_DataUtil.get_meta_dict_data("ScriptCommentMenu_ScriptUtil_get_built_in_class")
return ScriptCommentMenu_DataUtil.get_value_or_set(_class_db, _class, func():
var script = GDScript.new()
script.source_code = "var type = " + _class
if script.reload() == OK:
var obj = script.new()
_class_db[_class] = obj.type
return _class_db[_class]
else:
push_error("错误的类名:", _class)
return null
)
## 根据类名返回类对象
static func get_script_class(_class: StringName):
if ClassDB.class_exists(_class):
return null
var _class_db = ScriptCommentMenu_DataUtil.get_meta_dict_data("ScriptCommentMenu_ScriptUtil_get_script_class")
return ScriptCommentMenu_DataUtil.get_value_or_set(_class_db, _class, func():
var script = GDScript.new()
script.source_code = "var type = " + _class
if script.reload() == OK:
var obj = script.new()
_class_db[_class] = obj.type
return _class_db[_class]
else:
push_error("错误的类名:", _class)
return null
)
## 创建脚本
static func create_script(source_code: String) -> GDScript:
var data = ScriptCommentMenu_DataUtil.get_meta_dict_data("ScriptCommentMenu_ScriptUtil_create_script")
return ScriptCommentMenu_DataUtil.get_value_or_set(data, source_code.sha256_text(), func():
var script := GDScript.new()
script.source_code = source_code
script.reload()
return script
)
## 获取这个类的场景。这个场景的位置和名称需要和脚本一致,只有后缀名不一样。这个类不能是内部类
static func get_script_scene(script: GDScript) -> PackedScene:
var data = ScriptCommentMenu_DataUtil.get_meta_dict_data("ScriptCommentMenu_ScriptUtil_get_script_scene")
if data.has(script):
return data[script]
else:
var path := script.resource_path
if path == "":
return null
var ext := path.get_extension()
var file = path.substr(0, len(path) - len(ext))
var scene: PackedScene
if FileAccess.file_exists(file + "tscn"):
scene = ResourceLoader.load(file + "tscn", "PackedScene") as PackedScene
elif FileAccess.file_exists(file + "scn"):
scene = ResourceLoader.load(file + "scn", "PackedScene") as PackedScene
else:
printerr("这个类目录下没有相同名称的场景文件!")
return null
data[script] = scene
return scene
## 获取对象的类。如果是自定义类返回 [GDScript] 类;如果是内置类,则返回 [GDScriptNativeClass] 类
static func get_object_class(object: Object):
if object:
if object is Script:
return object
if object.get_script() != null:
return object.get_script()
return get_built_in_class (object.get_class())
return &""

View File

@ -1,88 +0,0 @@
#============================================================
# @sub Tem
#============================================================
# - datetime: 2022-07-17 16:32:29
#============================================================
class_name _ScriptMenu_SubItem
const MenuItemBuilder := ScriptCommentMenuConstant.MenuItemBuilder
var _editor_plugin = EditorPlugin.new()
var _util_script : ScriptCommentMenu_ScriptUtil
var _util_script_editor : ScriptCommentMenuConstant.ScriptEditorUtil
#============================================================
# Set/Get
#============================================================
func get_editor_interface() -> EditorInterface:
return _editor_plugin.get_editor_interface()
func get_script_editor_util():
return _util_script_editor
func get_script_util() -> ScriptCommentMenu_ScriptUtil:
return _util_script
#============================================================
# 自定义
#============================================================
## 外部调用初始化菜单
##[br]
##[br][code]menu_button[/code] 菜单按钮
func init_menu(menu_button: MenuButton) -> void:
if not menu_button.has_meta("IsInit"):
_util_script_editor = ScriptCommentMenuConstant.ScriptEditorUtil.new()
_util_script = ScriptCommentMenu_ScriptUtil.new()
menu_button.set_meta("IsInit", {
"_util_script_editor": _util_script_editor,
"_util_script": _util_script,
})
var data : Dictionary = menu_button.get_meta("IsInit")
for property in data:
var value = data[property]
set(property, value)
_init_menu(menu_button)
## 添加分隔符
func add_separator(menu_button: MenuButton):
(MenuItemBuilder.instance()
.set_menu_by_menu_button(menu_button)
.add_separator()
.build()
)
## 添加菜单
func add_menu_item(menu_button: MenuButton, name: String, key_map: Dictionary, callable: Callable):
# 添加菜单
(MenuItemBuilder.instance()
.set_menu(menu_button.get_popup())
.set_item_name(name)
.set_connect(callable)
.set_key(key_map.get("key", false))
.set_ctrl(key_map.get("ctrl", false))
.set_shift(key_map.get("shift", false))
.set_alt(key_map.get("alt", false))
.build()
)
## 重写方法,初始化菜单
##[br]
##[br][code]menu_button[/code] 菜单按钮
func _init_menu(menu_button: MenuButton) -> void:
pass
## 卸载子项
func _uninstall():
pass

View File

@ -1,134 +0,0 @@
#============================================================
# Comment
#============================================================
# - datetime: 2022-07-17 14:49:15
#============================================================
## 脚本注释
class_name _ScriptMenu_Comments
extends _ScriptMenu_SubItem
const SEPARATE_LENGTH = 60
var util_script_editor := ScriptCommentMenuConstant.ScriptEditorUtil.new()
var util_script := ScriptCommentMenu_ScriptUtil.new()
var regex = RegEx.new()
#============================================================
# 内置
#============================================================
func _init():
var pattern = "(?<indent>\\s*)(static\\s+)?func\\s+(?<method>[^\\(]+)"
regex.compile(pattern)
#============================================================
# 自定义
#============================================================
#(override)
func _init_menu(menu_button: MenuButton):
# 设置添加菜单项
var menu : PopupMenu = menu_button.get_popup()
add_menu_item(menu_button, "脚本注释", {}, _script_comment)
add_separator(menu_button)
add_menu_item(menu_button, "方法注释", {
"key": KEY_C,
"ctrl": true,
"shift": true,
}, _func_comment)
add_menu_item(menu_button, "类别分隔", {
"key": KEY_SLASH,
"ctrl": true,
"shift": true,
}, _category_comment)
#============================================================
# 功能
#============================================================
## 方法注释
func _func_comment():
var text_edit : TextEdit = util_script_editor.get_current_code_editor()
var line : int = text_edit.get_caret_line()
for i in range(line, text_edit.get_line_count()):
var line_code : String = text_edit.get_line(i)
var result = regex.search(line_code)
if result:
var method = result.get_string("method").strip_edges()
printt(method
, util_script_editor.get_current_script()
, util_script_editor.get_current_script().resource_path
)
var indent = result.get_string("indent")
var data = util_script.find_method_data(util_script_editor.get_current_script(), method)
if data.size() == 0:
printerr('没有找到', method,'方法的数据,脚本是否还未保存?')
return
var code : String = "## %s\n" % data['name']
if data['args'].size() > 0:
code += (indent + "##[br]\n")
for arg in data['args']:
code += (indent + "##[br][code]%s[/code] \n" % arg['name'])
if data['return']['type'] != TYPE_NIL:
code += (indent + "##[br][code]return[/code] ")
code = code.trim_suffix("\n")
util_script_editor.insert_code_current_pos(code, true)
break
## 脚本注释
func _script_comment():
var script = util_script_editor.get_current_script()
if script == null:
return
var separa = "=".repeat(SEPARATE_LENGTH)
# 脚本名
var script_name = script.resource_path.get_file().get_basename().capitalize()
# 时间
var datetime = Time.get_datetime_dict_from_system()
var datetime_str = "%02d-%02d-%02d %02d:%02d:%02d" % [
datetime['year'], datetime['month'], datetime['day'],
datetime['hour'], datetime['minute'], datetime['second'],
]
var code = """#{sep}
# {name}
#{sep}
# - author: zhangxuetu
# - datetime: {datetime}
# - version: 4.0
#{sep}
""".format({
"sep": separa,
"name": script_name,
"datetime": datetime_str,
})
# 插入到顶部
var textedit = util_script_editor.get_current_code_editor()
textedit.set_caret_line(0)
textedit.set_caret_column(0)
textedit.insert_text_at_caret(code)
## 类别分隔
func _category_comment():
var separa = "=".repeat(SEPARATE_LENGTH)
var code = """#{sep}
#
#{sep}""".format({
"sep": separa,
})
var textedit = util_script_editor.get_current_code_editor()
textedit.set_caret_column(0)
textedit.insert_text_at_caret(code)
textedit.set_caret_line(textedit.get_caret_line() - 1)

View File

@ -1,53 +0,0 @@
#============================================================
# Dialog
#============================================================
# - datetime: 2022-07-17 15:49:30
#============================================================
@tool
extends Control
signal selected_method(method_names : Array)
const SCRIPT_METHOD_LIST_SCRIPT = preload("res://addons/script_comment_menu/sub_item/override/scene/script_method_list.gd")
const CHECK_LABEL = preload("res://addons/script_comment_menu/sub_item/override/scene/check_label.gd")
@onready var confirmation_dialog = find_child("ConfirmationDialog")
@onready var script_method_list = find_child("ScriptMethodList") as SCRIPT_METHOD_LIST_SCRIPT
#============================================================
# 自定义
#============================================================
## 显示弹窗
func show_popup(script: Script):
confirmation_dialog.popup_centered_ratio(0.6)
script_method_list.update_data(script)
#============================================================
# 自定义
#============================================================
func _ready():
confirmation_dialog.confirmed.connect(_confirmed)
if not Engine.is_editor_hint():
confirmation_dialog.popup_centered()
#============================================================
# 连接信号
#============================================================
func _confirmed():
var selected_items = script_method_list.get_selected_items()
var method_list = {}
for item in selected_items:
item = item as CHECK_LABEL
var method_name : String = item.text
method_list[method_name] = null
item.selected = false
selected_method.emit(method_list.keys())

View File

@ -1,22 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://cnx2ri54w1dpr"]
[ext_resource type="Script" path="res://addons/script_comment_menu/sub_item/override/dialog.gd" id="1_isfs8"]
[ext_resource type="PackedScene" uid="uid://btcf0syabq2et" path="res://addons/script_comment_menu/sub_item/override/scene/script_method_list.tscn" id="2_lxp0b"]
[node name="Dialog" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
mouse_filter = 2
script = ExtResource("1_isfs8")
metadata/_edit_lock_ = true
[node name="ConfirmationDialog" type="ConfirmationDialog" parent="."]
dialog_close_on_escape = false
[node name="ScriptMethodList" parent="ConfirmationDialog" instance=ExtResource("2_lxp0b")]
anchor_right = 0.0
anchor_bottom = 0.0
offset_right = 636.0
offset_bottom = 342.0

View File

@ -1,106 +0,0 @@
#============================================================
# Override
#============================================================
# - datetime: 2022-07-17 15:53:56
#============================================================
## 重写
class_name _ScriptMenu_Overrides
extends _ScriptMenu_SubItem
const DIALOG_SCRIPT = preload("dialog.gd")
const DIALOG_SCENE = preload("dialog.tscn")
var dialog = DIALOG_SCENE.instantiate() as DIALOG_SCRIPT
#============================================================
# 自定义
#============================================================
#(override)
func _init_menu(menu_button: MenuButton):
# 添加弹窗
dialog.selected_method.connect(_selected_method)
get_editor_interface().get_base_control().add_child(dialog)
dialog.theme = get_editor_interface().get_base_control().theme
# 添加菜单
add_separator(menu_button)
add_menu_item(menu_button, "重写方法", {
"key": KEY_O,
"ctrl": true,
"shift": true,
}, _show_popup)
#(override)
func _uninstall():
super._uninstall()
dialog.queue_free()
#============================================================
# 连接信号
#============================================================
## 显示弹窗
func _show_popup():
var script = get_script_editor_util().get_current_script() as Script
dialog.show_popup(script)
const FORMAT = """
#(override)
func {method_name}({arguments}){return_type}:
{return_value}super.{method_name}({parameters})
"""
func _selected_method(method_names : Array):
var text_edit = get_script_editor_util().get_current_code_editor() as TextEdit
var script = get_script_editor_util().get_current_script() as Script
var code : String = ""
var added = {}
for method_data in script.get_script_method_list():
if added.has(method_data['name']) or not method_data['name'] in method_names:
continue
added[method_data['name']] = null
var method_name = method_data['name']
var method_type = method_data.get("type", 0)
var method_args = method_data['args'] as Array
var method_return = method_data['return']
# 参数列表
var arguments : String = ", ".join(method_args.map(func(arg): return arg['name']))
arguments = arguments.strip_edges().trim_suffix(",")
# 类型
var return_type : String = ScriptCommentMenu_ScriptUtil.get_type_name(method_type)
var return_value : String = ""
if return_type == "null":
return_type = ""
if return_type != "":
return_type = " -> " + return_type
return_value = "return "
code += FORMAT.format({
"method_name": method_name,
"method_type": ScriptCommentMenu_ScriptUtil.get_type_name(method_type),
"return_type": return_type,
"return_value": "",
"arguments": arguments,
"parameters": arguments,
})
text_edit.set_caret_column(0)
text_edit.insert_text_at_caret(code)

View File

@ -1,51 +0,0 @@
#============================================================
# Check check_box
#============================================================
# - datetime: 2022-07-16 22:30:22
#============================================================
@tool
extends HBoxContainer
signal pressed
@export
var text : String = "":
set(value):
text = value
if check_box == null:
await ready
check_box.text = value
get:
return check_box.text
@export
var selected : bool = false :
set(value):
selected = value
if check_box == null:
await ready
check_box.button_pressed = value
get:
return check_box.button_pressed
@export
var color : Color = Color.WHITE :
set(value):
color = value
if check_box == null:
await ready
check_box.modulate = color
@onready var check_box = $CheckBox as CheckBox
func _ready():
check_box.pressed.connect(_pressed)
func _pressed():
pressed.emit()

View File

@ -1,15 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://bvoxy56c0um5v"]
[ext_resource type="Script" path="res://addons/script_comment_menu/sub_item/override/scene/check_label.gd" id="1_7qjn7"]
[node name="CheckLabel" type="HBoxContainer"]
offset_right = 484.0
offset_bottom = 24.0
size_flags_horizontal = 3
size_flags_vertical = 0
script = ExtResource("1_7qjn7")
[node name="CheckBox" type="CheckBox" parent="."]
offset_right = 484.0
offset_bottom = 24.0
size_flags_horizontal = 3

View File

@ -1,88 +0,0 @@
#============================================================
# Item List
#============================================================
# - datetime: 2022-07-17 15:02:44
#============================================================
@tool
extends VBoxContainer
const CHECK_LABEL_SCENE = preload("check_label.tscn")
const CHECK_LABEL_SCRIPT = preload("check_label.gd")
var last_press_check : CHECK_LABEL_SCRIPT
## 添加 Item
##[br]
##[br][code]label[/code]
##[br][code]color[/code]
##[br]
##[br][code]return[/code]
func add_item(label: String, color : Color = Color.WHITE) -> CHECK_LABEL_SCRIPT:
var check_label = CHECK_LABEL_SCENE.instantiate()
add_child(check_label)
check_label.text = label
check_label.color = color
# 上一次点击的对象
check_label.pressed.connect(_pressed.bind(check_label))
return check_label
## 添加组别标签
##[br]
##[br][code]text[/code] 标签名
func add_label(text: String):
var space = Control.new()
space.custom_minimum_size.y = 4
add_child(space)
var panel = Panel.new()
panel.custom_minimum_size.y = 1
add_child(panel)
var label = Label.new()
label.text = text
add_child(label)
## 获取所有项
func get_all_item() -> Array:
var list = []
for child in get_children():
if child is CHECK_LABEL_SCRIPT:
list.append(child)
return list
## 获取选中的项
func get_selected_items() -> Array:
var list = []
for item in get_all_item():
item = item as CHECK_LABEL_SCRIPT
if item.selected:
list.append(item)
return list
## 清除所有
func clear():
for child in get_children():
child.queue_free()
#============================================================
# 连接信号
#============================================================
func _pressed(check: CHECK_LABEL_SCRIPT):
# 如果是按着 Shift 键
var all_item = get_all_item()
if Input.is_key_pressed(KEY_SHIFT):
if last_press_check != check:
var last_index = all_item.find(last_press_check)
var curr_index = all_item.find(check)
var selected = check.selected
for i in range(last_index, curr_index, sign(curr_index - last_index)):
all_item[i].selected = selected
last_press_check = check

View File

@ -1,10 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dmiw7e671j5ox"]
[ext_resource type="Script" path="res://addons/script_comment_menu/sub_item/override/scene/item_list.gd" id="1_of1bk"]
[node name="ItemList" type="VBoxContainer"]
offset_right = 1024.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_of1bk")

View File

@ -1,69 +0,0 @@
#============================================================
# Script Method List
#============================================================
# - datetime: 2022-07-17 15:22:58
#============================================================
@tool
extends MarginContainer
const ITEM_LIST_SCRIPT = preload("item_list.gd")
@onready var item_list = $ScrollContainer/ItemList as ITEM_LIST_SCRIPT
@onready var scroll_container = $ScrollContainer
## 获取所有选中的项
func get_selected_items() -> Array:
return item_list.get_selected_items()
## 更新数据
##[br]
##[br][code]script[/code] 脚本
func update_data(script: Script):
# 获取脚本的继承的所有脚本类
var scripts = []
script = script.get_base_script()
while script != null:
scripts.append(script)
script = script.get_base_script()
# 显示脚本的数据
show_script_list_data(scripts)
# 滚动到下面
# await get_tree().create_timer(0.1).timeout
# scroll_container.scroll_vertical = 2000
## 展示脚本列表数据
##[br]
##[br][code]scripts[/code]
func show_script_list_data(scripts: Array):
item_list.clear()
# 已添加过的(防止重复获取)
var added : Dictionary = {}
# 开始遍历
scripts.reverse()
for script in scripts:
var path = script.resource_path.get_file()
item_list.add_label(path)
# 获取数据
var data = {}
for method_data in script.get_script_method_list():
var method_name : String = method_data['name']
if not added.has(method_name):
data[method_name] = method_data
added[method_name] = null
# 排序
var list = data.keys()
list.sort()
for key in list:
var method_data : Dictionary = data[key]
var method : String = method_data['name']
item_list.add_item(method)

View File

@ -1,17 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://btcf0syabq2et"]
[ext_resource type="Script" path="res://addons/script_comment_menu/sub_item/override/scene/script_method_list.gd" id="1_ao6ev"]
[ext_resource type="PackedScene" uid="uid://dmiw7e671j5ox" path="res://addons/script_comment_menu/sub_item/override/scene/item_list.tscn" id="1_rynrx"]
[node name="ScriptMethodList" type="MarginContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_ao6ev")
[node name="ScrollContainer" type="ScrollContainer" parent="."]
offset_right = 1024.0
offset_bottom = 600.0
[node name="ItemList" parent="ScrollContainer" instance=ExtResource("1_rynrx")]

View File

@ -1,7 +0,0 @@
class_name ScriptCommentMenuConstant
const AddMenu = preload("res://addons/script_comment_menu/util/add_menu.gd")
const MenuItemBuilder = preload("res://addons/script_comment_menu/util/menu_item_builder.gd")
const PopupMenuUtil = preload("res://addons/script_comment_menu/util/popup_menu_util.gd")
const ScriptEditorUtil = preload("res://addons/script_comment_menu/util/script_editor_util.gd")

View File

@ -1,106 +0,0 @@
extends EditorScript
const EditorUtil_PopupMenu = preload("popup_menu_util.gd")
var json = JSON.new()
var util_popup_menu = EditorUtil_PopupMenu.new()
func _run():
pass
var menu = MenuButton.new()
menu.text = "测试菜单"
add_editor_menu(menu)
await get_tree().create_timer(2).timeout
menu.queue_free()
var _top_container: HBoxContainer
func _get_top_container() -> HBoxContainer:
if _top_container == null:
for child in get_editor_interface().get_base_control().get_children():
if child is VBoxContainer:
_top_container = child.get_child(0)
break
return _top_container
var _editor_menu_container : HBoxContainer
func get_editor_menu_container() -> HBoxContainer:
if _editor_menu_container == null:
_editor_menu_container = _get_top_container().get_child(0)
return _editor_menu_container
func add_editor_menu(menu_button: MenuButton):
get_editor_menu_container().add_child(menu_button)
func get_tree():
return get_editor_interface().get_tree()
## 添加脚本菜单按钮
func add_script_editor_menu(menu_button: MenuButton, items: Array = []):
var popup = menu_button.get_popup()
for item in items:
if item.begins_with("-"):
popup.add_separator()
else:
while item.begins_with("-"):
item = item.trim_prefix("-")
popup.add_item(item)
var menu_container : Control
while true:
var tmp = get_editor_interface() \
.get_script_editor() \
.get_current_editor()
if tmp == null:
await Engine.get_main_loop().create_timer(1).timeout
continue
for i in 4:
tmp = tmp.get_parent_control()
if tmp == null:
break
if tmp == null:
await Engine.get_main_loop().create_timer(1).timeout
continue
menu_container = tmp.get_child(0) as Control
break
var node_index : int = 0
for i in range(menu_container.get_child_count() - 1, -1, -1):
if menu_container.get_child(i) is MenuButton:
node_index = i + 1
break
menu_container.add_child(menu_button)
menu_container.move_child(menu_button, node_index)
func connect_menu(menu, item_name: String, callable, method: String = ""):
var popup : PopupMenu
if menu is MenuButton:
popup = menu.get_popup()
elif menu is PopupMenu:
popup = menu
if method:
util_popup_menu.connect_popup_item(menu.get_popup(), item_name, callable, method)
else:
util_popup_menu.connect_popup_item(menu.get_popup(), item_name, callable.get_object(), callable.get_method())
static func add_menu_item_shortcut(
menu: MenuButton
, item_name: String
, keycode: int
, ctrl : bool
, alt : bool
, shift : bool
):
EditorUtil_PopupMenu.add_popup_shortcut(
menu.get_popup(), item_name, keycode, ctrl, alt, shift
)

View File

@ -1,139 +0,0 @@
#============================================================
# Menu Item Builder
#============================================================
# - datetime: 2022-07-17 14:14:42
#============================================================
# 菜单项建造器
var _menu : PopupMenu
var _name : String
var _method : Callable
var _short : Dictionary = {
key = KEY_NONE,
ctrl = false,
alt = false,
shift = false,
}
var _as_separator : bool = false
var _id : int = -1
#============================================================
# Set/Get
#============================================================
## 设置添加的菜单
##[br]
##[br][code]menu[/code] 菜单
func set_menu(menu: PopupMenu):
self._menu = menu
return self
## 设置菜单按钮对象
func set_menu_by_menu_button(menu_button: MenuButton):
set_menu( menu_button.get_popup() )
return self
## 设置菜单名
##[br]
##[br][code]name[/code]
func set_item_name(name: String):
self._name = name
return self
## 设置连接的方法
##[br]
##[br][code]method[/code] 连接的方法
func set_connect(method: Callable):
self._method = method
return self
## 设置快捷键
##[br]
##[br][code]key[/code] 按键 Key 值
##[br][code]ctrl[/code] Ctrl
##[br][code]alt[/code] Alt
##[br][code]shift[/code] Shift
func set_shortcut(
key: int
, ctrl: bool = false
, alt: bool = false
, shift: bool = false
):
self._short.key = key
self._short.ctrl = ctrl
self._short.alt = alt
self._short.shift = shift
return self
func set_key(key : int):
self._short.key = key
return self
func set_ctrl(value : bool = true):
self._short.ctrl = value
return self
func set_shift(value : bool = true):
self._short.shift = value
return self
func set_alt(value : bool = true):
self._short.alt = value
return self
#============================================================
# 自定义
#============================================================
## 实例化一个 Builder
##[br]
##[br][code]return[/code] 返回实例化对象
static func instance():
var builder = load("res://addons/script_comment_menu/util/menu_item_builder.gd").new()
return builder
## 添加分隔符
func add_separator():
_as_separator = true
return self
## 构建添加
##[br]
##[br][code]return[/code] 返回菜单的 id 值
func build() -> int:
_id = _menu.item_count
# 引用这个 builder不这样则会因为引用消失而造成下面的 _id_pressed 失效
_menu.set_meta("MenuItemMenu_%d" % _id, self)
if _menu.id_pressed.connect( _id_pressed ) != OK:
printerr(" > id_pressed 信号连接方法失败")
if not _as_separator:
_menu.add_item(_name)
else:
_menu.add_separator(_name)
if _short.key != KEY_NONE:
var input = InputEventKey.new()
input.keycode = _short.key
input.ctrl_pressed = _short.ctrl
input.alt_pressed = _short.alt
input.shift_pressed = _short.shift
var shortcut = Shortcut.new()
shortcut.events.append(input)
_menu.set_item_shortcut(_id, shortcut)
return _id
#============================================================
# 连接信号
#============================================================
func _id_pressed(id):
if id == _id:
_method.call()

View File

@ -1,62 +0,0 @@
extends RefCounted
static func find_popup_menu_id(popup: PopupMenu, item_name: String) -> int:
for idx in popup.get_item_count():
# 找到这个菜单
if popup.get_item_text(idx) == item_name:
return idx
return -1
static func add_popup_shortcut(
popup: PopupMenu
, item_name: String
, keycode: int
, ctrl : bool
, alt : bool
, shift : bool
):
var idx = find_popup_menu_id(popup, item_name)
if idx > -1:
var shortcut = Shortcut.new()
var input = InputEventKey.new()
input.keycode = keycode
input.ctrl_pressed = ctrl
input.alt_pressed = alt
input.shift_pressed = shift
shortcut.events.append(input)
popup.set_item_shortcut(idx, shortcut)
else:
printerr("没有这个名称 ", item_name, " 的菜单项")
var _popup_data := {}
func connect_popup_item(popup: PopupMenu, item_name: String, target: Object, method: String) -> int:
var idx = find_popup_menu_id(popup, item_name)
if idx > -1:
if not popup.id_pressed.is_connected(self._popup_id_pressed):
popup.id_pressed.connect(self._popup_id_pressed.bind(popup))
if not _popup_data.has(popup):
_popup_data[popup] = {}
if not _popup_data[popup].has(idx):
_popup_data[popup][idx] = []
# 记录这个菜单的 id 的点击数据
_popup_data[popup][idx].append({
"target": target,
"method": method,
})
else:
printerr("这个菜单 popup 没有 ", item_name, " 的菜单项")
return idx
func _popup_id_pressed(idx: int, popup: PopupMenu):
if _popup_data.has(popup):
var connected_data_list : Array = _popup_data[popup][idx]
for data in connected_data_list:
var target : Object = data['target']
var method : String = data['method']
target.call(method)

View File

@ -1,65 +0,0 @@
extends EditorScript
## 当前代码编辑器
func get_current_code_editor() -> TextEdit:
return (get_editor_interface()
.get_script_editor()
.get_current_editor()
.get_base_editor()) as TextEdit
## 当前脚本的代码
func get_current_script_code() -> String:
return get_current_code_editor().text
var _script_popup_id : int = -1
var _script_popup := {}
## 添加脚本弹窗
## @return 返回添加的弹窗菜单的[code]id[/code]
func add_script_popup(popup: PopupMenu) -> int:
_script_popup_id += 1
_script_popup[_script_popup_id] = popup
get_editor_interface().get_script_editor().add_child(popup)
return _script_popup_id
## 弹出菜单
## @id 菜单 [code]id[/code] 为 [method add_script_popup]add_script_popup[/method] 后返回的值
func popup_menu(id: int):
if _script_popup.has(id):
var editor = get_current_code_editor() as TextEdit
var popup : PopupMenu = _script_popup[id]
popup.position = (get_current_code_editor().global_position
+ get_current_code_editor().get_caret_draw_pos()
+ Vector2(0, 50)
)
popup.popup()
func get_current_script() -> Script:
return get_editor_interface() \
.get_script_editor() \
.get_current_script()
func insert_code_current_pos(code: String, insert_first: bool = false):
var textedit = get_current_code_editor()
if insert_first:
textedit.set_caret_column(0)
textedit.insert_text_at_caret(code)
func _run():
pass
var popup = PopupMenu.new()
popup.add_item("test_01")
popup.add_item("test_02")
popup.add_item("test_03")
var id = add_script_popup(popup)
popup_menu(id)
await get_editor_interface().get_tree().create_timer(1).timeout
popup.queue_free()

View File

@ -1,7 +0,0 @@
[plugin]
name="table-data-editor"
description=""
author="张学徒"
version="1.3"
script="plugin.gd"

View File

@ -1,64 +0,0 @@
#============================================================
# Plugin
#============================================================
# - datetime: 2022-11-27 22:27:12
#============================================================
@tool
extends EditorPlugin
const MAIN = preload("src/table_data_editor/table_data_editor.tscn")
var main := MAIN.instantiate() as TableDataEditor
# 第一次显示出来
var first_show := false
func _ready():
if Time.get_ticks_msec() < 5000:
await Engine.get_main_loop().create_timer(5).timeout
main.visible = false
get_editor_interface().get_editor_main_screen().add_child(main)
main.call_deferred("set_anchors_preset", Control.PRESET_FULL_RECT)
main.set_deferred("size", main.get_parent().size)
main.get_child(0).set_deferred("size", main.size)
# 创建新文件时进行扫描
main.created_file.connect(func(path):
await Engine.get_main_loop().create_timer(0.1).timeout
get_editor_interface() \
.get_resource_filesystem() \
.scan.call_deferred()
)
func _exit_tree() -> void:
main.queue_free()
func _has_main_screen():
return true
func _make_visible(visible):
main.visible = visible
func _get_plugin_name():
return "TableDataEditor"
func _get_plugin_icon():
var icon = get_editor_interface() \
.get_base_control() \
.get_theme_icon("GridContainer", "EditorIcons") as Texture2D
icon = icon.duplicate(true)
var image = icon.get_image() as Image
var image_size = image.get_size()
var color : Color
for x in image_size.x:
for y in image_size.y:
color = image.get_pixel(x, y)
if color.a != 0:
color = get_editor_interface().get_editor_settings().get_setting("text_editor/theme/highlighting/text_color")
image.set_pixel(x, y, color)
var texture = ImageTexture.create_from_image(image)
return texture

View File

@ -1,86 +0,0 @@
#============================================================
# Project Data
#============================================================
# - author: zhangxuetu
# - datetime: 2023-05-17 17:37:56
# - version: 4.0
#============================================================
## 项目数据
##
##整个表格项目中的之前的缓存数据,也可视作整个项目数据
##[br]启动这个节点后
class_name TableDataEditor_CacheData
const CACHE_DATA_PATH = "res://.godot/table_data_editor/~json_edit_grid_cache_data.gdata"
## 最后一次操作的文件的路径
var last_operation_path : String = ""
# 最近打开过的文件
var recently_opend_paths = {}:
set(v):
if v is Array:
recently_opend_paths = {}
for item in v:
recently_opend_paths[item] = null
elif v is Dictionary:
recently_opend_paths = v
else:
assert(false, "错误的数据类型")
# 弹窗路径
var dialog_path : String = ""
#============================================================
# SetGet
#============================================================
func get_recently_opend_paths() -> Array:
if recently_opend_paths is Array:
return recently_opend_paths
return recently_opend_paths.keys()
#============================================================
# 自定义
#============================================================
static func instance() -> TableDataEditor_CacheData:
var script = TableDataEditor_CacheData as GDScript
const KEY = "instance"
if script.has_meta(KEY):
return script.get_meta(KEY)
else:
var object = TableDataEditor_CacheData.new()
var data
if FileAccess.file_exists(CACHE_DATA_PATH):
var bytes = FileAccess.get_file_as_bytes(CACHE_DATA_PATH)
data = bytes_to_var(bytes)
if data is Dictionary:
TableDataUtil.Classes.set_property_by_dict(object, data)
script.set_meta(KEY, object)
return object
## 保存数据
static func save_data() -> void:
var data = TableDataUtil.Classes.get_dict_by_property(instance())
TableDataUtil.Files.save_data(CACHE_DATA_PATH, data)
## 存在打开的文件
func exists_opened_path() -> bool:
return last_operation_path != "" and FileAccess.file_exists(last_operation_path)
func update_last_operation_path(path: String):
path = path.strip_edges()
last_operation_path = path
if path != "":
if not recently_opend_paths is Dictionary:
recently_opend_paths = {}
if FileAccess.file_exists(path):
recently_opend_paths[path] = null

View File

@ -1,284 +0,0 @@
#============================================================
# Export Preview
#============================================================
# - author: zhangxuetu
# - datetime: 2022-11-27 17:45:09
# - version: 4.0
#============================================================
# 预览导出
@tool
class_name ExportPreviewWindow
extends Window
## 最大示例数量
const EXAMPLE_COUNT : int = 5
## 导出 json 数据
signal exported(path: String, type, data)
@export_node_path("TableDataEditor") var _table_data_editor : NodePath
@onready var table_data_editor : TableDataEditor = get_node(_table_data_editor)
var __init_node = InjectUtil.auto_inject(self, "_")
var _text_box : TextEdit
var _head_as_key_panel : Control
var _head_line_box : SpinBox
var _save_dialog : FileDialog
var _compact : CheckBox
var _select_items : Control
var _selected_item_param : Control
var _resource_class_name : LineEdit
## 指定的 head 列数对应的值内容。_head_map[列数] = 值内容
var _head_map : Dictionary = {}
## 类型选项按钮组
var _button_group : ButtonGroup
#============================================================
# 内置
#============================================================
func _ready() -> void:
_button_group = _select_items.get_child(0).button_group as ButtonGroup
_button_group.pressed.connect(func(button):
for child in _selected_item_param.get_children():
if child is Control:
child.visible = false
var item_node : Control = _selected_item_param.get_node_or_null(str(button.name))
if item_node:
item_node.visible = true
update_text_box_content()
)
#============================================================
# SetGet
#============================================================
## 获取头部字段行。列值对应字段值
func get_head_map() -> Dictionary:
var head_row_number : int = _head_line_box.value
var data_set = table_data_editor.get_table_edit().get_data_set()
var head_row_data : Dictionary = data_set.grid_data.get(head_row_number, {})
return head_row_data
## 将有值的行的数据进行保存
func get_data_by_head_row() -> Array:
var result : Array = []
var head_row_number : int = _head_line_box.value
var data_set = table_data_editor.get_table_edit().get_data_set()
var head_row_data : Dictionary = data_set.grid_data.get(head_row_number, {})
head_row_number += 1
for row in range(head_row_number, data_set.grid_data.size() + 1):
var data = {}
var row_data = data_set.grid_data[row]
for column in head_row_data:
data[head_row_data[column]] = row_data.get(column, "")
result.append(data)
return result
## 获取 CSV 格式数据
func get_csv_data() -> Array[String]:
var data_set = table_data_editor.get_table_edit().data_set as TableDataEditor_TableDataSet
var max_column : int = data_set.get_max_column()
if max_column == 0:
return []
var csv_list : Array[String] = []
for row in data_set.get_row_list():
var line : Array = []
for column in range(1, max_column + 1):
line.append(
JSON.stringify(data_set.get_value(Vector2i(column, row)))
)
csv_list.append(",".join(line))
return csv_list
## 获取转为资源的数据
func get_resources_by_path(path: String) -> Array[Resource]:
var head_row_data : Dictionary = get_head_map()
var head_row_number : int = _head_line_box.value
var data_set = table_data_editor.get_table_edit().get_data_set()
var row_list = data_set.get_row_list()
for idx in row_list.size():
var row = row_list[idx]
if row > head_row_number:
row_list = row_list.slice(idx, row_list.size())
break
# 类名
var r_class_name : String = _resource_class_name.text.strip_edges()
# 属性列表
var propertys : Array = []
for column in head_row_data:
# 寻找这个字段有数据的行,判断数据类型
var value = ""
for row in row_list:
value = data_set.grid_data[row].get(column)
if value != "":
break
# 判断数据类型
var type = "String"
if value != "":
var json = JSON.parse_string(value)
match typeof(json):
TYPE_STRING, TYPE_NIL: type = "String"
TYPE_INT: type = "int"
TYPE_FLOAT: type = "float"
TYPE_BOOL: type = "bool"
TYPE_ARRAY: type = "Array"
TYPE_DICTIONARY: type = "Dictionary"
TYPE_COLOR: type = "Color"
TYPE_VECTOR2: type = "Vector2"
TYPE_VECTOR2I: type = "Vector2i"
TYPE_VECTOR3: type = "Vector3"
TYPE_VECTOR3I: type = "Vector3i"
TYPE_VECTOR4: type = "Vector4"
TYPE_VECTOR4I: type = "Vector4i"
_: "String"
# 生成 @export s属性
var property = head_row_data[column]
printt(column, property, value)
propertys.append("@export var %s : %s " % [property, type])
# 生成脚本
var script = GDScript.new()
script.source_code = """# {ScriptName}
{ClassName}extends Resource
{Propertys}
""".format({
"ScriptName": path.get_file(),
"ClassName": ("class_name %s\n" % [r_class_name]) if r_class_name else "",
"Propertys": "\n".join(propertys),
})
ResourceSaver.save(script, path)
# 生成资源
var resources : Array[Resource] = []
var new_script = load(path) as GDScript
for row in row_list:
# 生成数据
var data = {}
var row_data = data_set.grid_data[row]
for column in head_row_data:
data[head_row_data[column]] = row_data.get(column, "")
# 生成资源。避免 new 时跟已有的类冲突造成的报错
var resource = Resource.new()
resource.set_script(new_script)
for property in data:
resource[property] = data[property]
resources.append(resource)
return resources
#============================================================
# 自定义
#============================================================
func _data_format(data) -> String:
return JSON.stringify(data, "" if _compact.button_pressed else "\t")
func _update_by_head_row():
var data_list = get_data_by_head_row()
var examples = []
for i in range(min(data_list.size(), EXAMPLE_COUNT)):
examples.append(data_list[i])
_text_box.text = JSON.stringify(examples, "\t")
func _update_by_csv():
var data_list = get_csv_data()
var examples = []
for i in range(min(data_list.size(), EXAMPLE_COUNT)):
examples.append(data_list[i])
_text_box.text = "\n".join(examples)
# 更新文本框的内容
func update_text_box_content():
_text_box.text = ""
match _button_group.get_pressed_button().name:
"json", "resource":
_update_by_head_row()
"csv":
_update_by_csv()
#============================================================
# 连接信号
#============================================================
func _on_head_line_box_value_changed(value: float) -> void:
update_text_box_content()
func _on_export_pressed() -> void:
var extension = str(_button_group.get_pressed_button().name)
if extension == "resource":
var r_class_name = _resource_class_name.text.strip_edges()
if r_class_name == "":
r_class_name = "res_0"
_save_dialog.current_file = r_class_name.to_snake_case() + ".gd"
else:
_save_dialog.current_file = "new_file." + extension
_save_dialog.popup_centered_ratio(0.5)
func _on_save_dialog_file_selected(path: String) -> void:
print(path)
var data
var type = str(_button_group.get_pressed_button().name)
match type:
"csv":
data = "\n".join(get_csv_data())
TableDataUtil.Files.save_as_string( path, data )
# 导出的文件保持默认文件,不作为翻译文件
var keep_import_path = path + ".import"
TableDataUtil.Files.save_as_string(keep_import_path, '[remap]\n\nimporter="keep"\n\n')
"json":
data = get_data_by_head_row()
TableDataUtil.Files.save_as_string( path, _data_format(data) )
"resource":
data = get_resources_by_path(path)
var idx : int = 0
var dir = path.get_base_dir()
var filename = path.get_file().get_basename()
for resource in data:
var file_path = dir.path_join("%s_%002d.tres" % [filename, idx])
while FileAccess.file_exists(file_path):
idx += 1
file_path = dir.path_join("%s_%002d.tres" % [filename, idx])
ResourceSaver.save(resource, file_path)
idx += 1
_save_dialog.current_path = path
self.hide()
print("[ ExportPreview ] 保存数据:", path)
self.exported.emit(path, type, data )
func _on_cancel_pressed():
self.hide()

View File

@ -1,181 +0,0 @@
[gd_scene load_steps=6 format=3 uid="uid://cqpmbxqgny2kq"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/export_preview/export_preview_window.gd" id="1_h8l6h"]
[sub_resource type="ButtonGroup" id="ButtonGroup_pxfjs"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_2ocp7"]
[sub_resource type="InputEventKey" id="InputEventKey_s3ym3"]
pressed = true
keycode = 4194305
[sub_resource type="Shortcut" id="Shortcut_5m7cm"]
events = [SubResource("InputEventKey_s3ym3")]
[node name="export_preview_window" type="Window"]
title = "Export"
size = Vector2i(800, 400)
wrap_controls = true
exclusive = true
script = ExtResource("1_h8l6h")
[node name="export_preview" type="MarginContainer" parent="."]
editor_description = "导出预览"
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Panel" type="Panel" parent="export_preview"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="export_preview"]
layout_mode = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="export_preview/MarginContainer"]
layout_mode = 2
[node name="HSplitContainer" type="HSplitContainer" parent="export_preview/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
split_offset = 200
[node name="VBoxContainer" type="VBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
layout_mode = 2
[node name="select_items" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="csv" type="CheckBox" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/select_items"]
layout_mode = 2
button_pressed = true
button_group = SubResource("ButtonGroup_pxfjs")
text = "CSV"
[node name="json" type="CheckBox" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/select_items"]
layout_mode = 2
button_group = SubResource("ButtonGroup_pxfjs")
text = "JSON"
[node name="resource" type="CheckBox" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/select_items"]
layout_mode = 2
button_group = SubResource("ButtonGroup_pxfjs")
text = "Resource"
[node name="VSeparator" type="VSeparator" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 16
[node name="selected_item_param" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
[node name="csv" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="Label" type="Label" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/csv"]
layout_mode = 2
text = "delim"
[node name="LineEdit" type="LineEdit" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/csv"]
layout_mode = 2
text = ","
placeholder_text = "表格间的分隔符,默认为 ,"
[node name="json" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param"]
visible = false
layout_mode = 2
[node name="Label" type="Label" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/json"]
layout_mode = 2
tooltip_text = "作为 key 的行"
text = "Key Row: "
[node name="head_line_box" type="SpinBox" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/json"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "作为 key 的行"
min_value = 1.0
max_value = 10000.0
value = 1.0
[node name="compact" type="CheckBox" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/json"]
unique_name_in_owner = true
visible = false
layout_mode = 2
tooltip_text = "紧凑的格式导出这样JSON会占用空间会最小。数据量不是非常大可以不用勾选"
text = "compact"
[node name="resource" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param"]
visible = false
layout_mode = 2
[node name="Label" type="Label" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/resource"]
layout_mode = 2
text = "ClassName"
[node name="resource_class_name" type="LineEdit" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/resource"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
placeholder_text = "Class Name"
[node name="Label" type="Label" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
modulate = Color(1, 1, 1, 0.462745)
layout_mode = 2
text = "Sample data in the first few lines..."
[node name="text_box" type="TextEdit" parent="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
editable = false
[node name="HBoxContainer" type="HBoxContainer" parent="export_preview/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Control" type="Control" parent="export_preview/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="export" type="Button" parent="export_preview/MarginContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(100, 32)
layout_mode = 2
text = "Export"
[node name="VSeparator" type="VSeparator" parent="export_preview/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/separation = 32
theme_override_styles/separator = SubResource("StyleBoxEmpty_2ocp7")
[node name="cancel" type="Button" parent="export_preview/MarginContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(80, 0)
layout_mode = 2
shortcut = SubResource("Shortcut_5m7cm")
text = "Cancel"
[node name="Control3" type="Control" parent="export_preview/MarginContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="save_dialog" type="FileDialog" parent="."]
unique_name_in_owner = true
size = Vector2i(375, 161)
access = 2
filters = PackedStringArray("*.json; JSON", "*.csv; CSV", "*.tres; TRES", "*.res; RES", "*.gd; GDScript")
[connection signal="value_changed" from="export_preview/MarginContainer/VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/selected_item_param/json/head_line_box" to="." method="_on_head_line_box_value_changed"]
[connection signal="pressed" from="export_preview/MarginContainer/VBoxContainer/HBoxContainer/export" to="." method="_on_export_pressed"]
[connection signal="pressed" from="export_preview/MarginContainer/VBoxContainer/HBoxContainer/cancel" to="." method="_on_cancel_pressed"]
[connection signal="file_selected" from="save_dialog" to="." method="_on_save_dialog_file_selected"]

View File

@ -1,106 +0,0 @@
#============================================================
# File Data
#============================================================
# - author: zhangxuetu
# - datetime: 2023-03-23 23:30:46
# - version: 4.0
#============================================================
## 当前文件数据
##
##新建/打开/存储 处理其中的数据。
##[br]文件相关的数据都在这里保存,加载存储获取都在这里,这样保证数据不会乱
class_name TableDataEditor_FileData
## 原始数据
var origin_data : Dictionary
# 数据格式版本
var version:
get: return 1_3_0
# 数据集
var data_set = TableDataEditor_TableDataSet.new():
set(v):
data_set = TableDataEditor_TableDataSet.new(v) \
if v is Dictionary \
else v
## 列宽数据
var column_width : Dictionary = {}
## 行高数据
var row_height : Dictionary = {}
## 编辑框大小
var edit_dialog_size : Vector2 = Vector2(100, 50)
## 文件路径
var path : String = ""
#============================================================
# SetGet
#============================================================
func is_empty() -> bool:
return origin_data.is_empty()
func is_new_file() -> bool:
return path.is_empty()
#============================================================
# 内置
#============================================================
func _init(origin_data: Dictionary):
self.origin_data = origin_data
# 加载旧版本数据
var version = origin_data.get("version")
if not ((version is float or version is int) and version >= 1.3):
var grid_data : Dictionary = origin_data.get("grid_data", {})
for coords in grid_data:
self.data_set.set_value(coords, grid_data[coords])
# 根据字典数据设置当前对象属性
TableDataUtil.Classes.set_property_by_dict(self, origin_data)
#============================================================
# 自定义
#============================================================
## 加载文件。
static func load_file(path: String) -> TableDataEditor_FileData:
if FileAccess.file_exists(path):
var data = TableDataUtil.Files.load_file(path)
if not data is Dictionary:
var reader = FileAccess.open(path, FileAccess.READ)
if reader:
data = reader.get_var()
if not data is Dictionary:
data = {}
var file_data = TableDataEditor_FileData.new(data)
file_data.path = path
return file_data
else:
return TableDataEditor_FileData.new({})
## 保存当前文件的数据
##[br]
##[br][code]saved_path[/code] 保存到的路径,默认不传参数保存为当前路径,
##如果当前没有设置路径则会报错,所以第一次要传入路径进行保存
func save_data(saved_path: String = "") -> bool:
if saved_path == "":
saved_path = self.path
self.path = saved_path
assert(self.path.strip_edges() != "", "当前没有设置文件路径")
# 获取数据
var data : Dictionary = {}
var propertys = TableDataUtil.Classes.get_propertys(self.get_script())
for property in propertys:
if property in self:
data[property] = self[property]
data["data_set"] = data_set.get_config_data()
return TableDataUtil.Files.save_data(self.path, data)

View File

@ -1,331 +0,0 @@
#============================================================
# Menu List
#============================================================
# - author: zhangxuetu
# - datetime: 2022-11-27 01:01:10
# - version: 4.0
#============================================================
## 菜单列表
@tool
class_name MenuList
extends MenuBar
## 菜单被点击
##[br]
##[br][code]idx[/code] 菜单的索引值
##[br][code]menu_path[/code] 菜单路径
signal menu_pressed(idx: int, menu_path: StringName)
## 复选框状态发生切换
signal menu_check_toggled(idx: int, menu_path: StringName)
# 自动增长的菜单 idx。用以下面添加菜单项时记录添加的菜单的 idx
var _auto_increment_menu_idx := -1
# 菜单路径对应 PopupMenu
var _menu_path_to_popup_menu_map := {}
# 菜单的 idx 对应的菜单路径
var _idx_to_menu_path_map := {}
# 菜单路径对应的菜单 idx
var _menu_path_to_idx_map := {}
# 子节点路径
var _child_menu_path_idx_list := {}
#=====================================================
# Set/Get
#=====================================================
## 获取弹窗菜单
##[br]
##[br][code]menu_path[/code] 这个菜单路径的父弹窗菜单节点
func get_menu(menu_path: StringName) -> PopupMenu:
return _menu_path_to_popup_menu_map.get(menu_path) as PopupMenu
## 添加快捷键
##[br]
##[br][code]menu_path[/code] 菜单路径
##[br][code]data[/code] 快捷键数据示例数据ctrl + shift + C 快捷键
##[codeblock]
##{
## "ctrl": true,
## "shift": true,
## "keycode": KEY_C,
##}
##[/codeblock]
func set_menu_shortcut(menu_path: StringName, data: Dictionary):
var shortcut = Shortcut.new()
var input = InputEventKey.new()
shortcut.events.append(input)
input.keycode = data.get("keycode", 0)
input.ctrl_pressed = data.get("ctrl", false)
input.alt_pressed = data.get("alt", false)
input.shift_pressed = data.get("shift", false)
var popup_menu := _menu_path_to_popup_menu_map.get(menu_path) as PopupMenu
var menu_name = menu_path.get_file()
if popup_menu:
for i in popup_menu.item_count:
if popup_menu.get_item_text(i) == menu_name:
popup_menu.set_item_shortcut(i, shortcut)
break
# 获取菜单的 ID
func _get_menu_id(menu_path: String) -> int:
return _menu_path_to_idx_map.get(menu_path, -1)
func _execute_menu_by_path(menu_path: StringName, method_name: String, params: Array = []):
var menu = get_menu(menu_path)
var idx = get_menu_idx(menu_path)
if menu and idx > 0:
return menu.call(method_name, params)
return null
## 设置菜单的可用性
func set_menu_disabled_by_path(menu_path: StringName, value: bool):
var menu = get_menu(menu_path)
var idx = get_menu_idx(menu_path)
if menu and idx > -1:
menu.set_item_disabled(idx, value)
func set_menu_as_checkable(menu_path: StringName, value: bool):
var menu = get_menu(menu_path)
var idx = get_menu_idx(menu_path)
if menu and idx > 0:
menu.set_item_as_checkable(idx, value)
func set_menu_check_by_path(menu_path: StringName, value: bool):
var menu = get_menu(menu_path)
var idx = get_menu_idx(menu_path)
if menu and idx > 0 and menu.is_item_checked(idx) != value:
menu.set_item_checked(idx, value)
self.menu_check_toggled.emit(idx, menu_path)
func get_menu_check_by_path(menu_path: StringName) -> bool:
var menu = get_menu(menu_path)
var idx = get_menu_idx(menu_path)
if menu and idx > 0:
return menu.is_item_checked(idx)
return false
func toggle_menu_check_by_path(menu_path: StringName) -> bool:
return _execute_menu_by_path(menu_path, "is_item_checked", [])
## 获取这个菜单的索引,如果不存在这个菜单,则返回 [code]-1[/code]
func get_menu_idx(menu_path: StringName) -> int:
var id = _menu_path_to_idx_map.get(menu_path, -1)
var menu = get_menu(menu_path)
if menu == null:
var parent_path = get_parent_menu_path(menu_path)
menu = get_menu(parent_path)
return menu.get_item_index(id)
## 获取这个索引的菜单路径
func get_menu_path(menu_path: StringName) -> StringName:
var idx = get_menu_idx(menu_path)
return _idx_to_menu_path_map.get(idx, "")
## 是否有这个菜单路径
func has_menu_path(menu_path: StringName) -> bool:
return _menu_path_to_popup_menu_map.has(menu_path)
## 获取父菜单路径
func get_parent_menu_path(menu_path: StringName) -> StringName:
var idx : int = _menu_path_to_idx_map.get(menu_path, -1)
if idx == -1:
return ""
for parent_path in _child_menu_path_idx_list:
var list = _child_menu_path_idx_list[parent_path] as Array
if list.has(idx):
return parent_path
return ""
#=====================================================
# 自定义方法
#=====================================================
## 初始化菜单。示例:
## [codeblock]
## init_menu({
## "File": ["Open", "Save", "Save As", {
## "Export As...": [ "Export PNG", "Export JPG" ]
## } ],
## "item": {
## "letter": ["a", "b", "c"],
## "number": [ "1", "2"],
## },
## })
## [/codeblock]
func init_menu(data: Dictionary):
add_menu(data, "/")
## 初始化快捷键,需要添加对应菜单。示例:
##[codeblock]
##{
## "/File/Open": {"keycode": KEY_O, "ctrl": true},
## "/File/Save": {"keycode": KEY_S, "ctrl": true},
##}
##[/codeblock]
func init_shortcut(data_list: Dictionary):
var data : Dictionary
for menu_path in data_list:
data = data_list[menu_path]
set_menu_shortcut(menu_path, data)
## 添加菜单项
##[br]
##[br][code]menu_data[/code] 这个菜单项包含的数据
##[br][code]parent_menu_path[/code] 父级菜单路径
func add_menu(menu_data, parent_menu_path: StringName):
var parent_popup_menu : PopupMenu = get_menu(parent_menu_path)
_auto_increment_menu_idx += 1
# 不是根路径时
if parent_menu_path != "/":
# Dictionary
if menu_data is Dictionary:
for menu_name in menu_data:
add_menu( menu_data[menu_name], parent_menu_path.path_join(menu_name))
# Array
elif menu_data is Array:
for data in menu_data:
add_menu(data, parent_menu_path)
# String
elif menu_data is String or menu_data is StringName:
# 添加菜单
if not _menu_path_to_popup_menu_map.has(parent_menu_path):
create_menu(parent_menu_path, null)
parent_popup_menu = get_menu(parent_menu_path)
# 不是 Array 和 Dictionary 类型时,只能是 String 类型了
var menu_name := StringName(menu_data)
if not menu_name.begins_with("-"):
var menu_path := "%s/%s" % [parent_menu_path, menu_name]
# 添加菜单项
parent_popup_menu.add_item(menu_name, _auto_increment_menu_idx)
_idx_to_menu_path_map[_auto_increment_menu_idx] = menu_path
_menu_path_to_idx_map[menu_path] = _auto_increment_menu_idx
_menu_path_to_popup_menu_map[menu_path] = parent_popup_menu
if not _child_menu_path_idx_list.has(parent_menu_path):
_child_menu_path_idx_list[parent_menu_path] = []
_child_menu_path_idx_list[parent_menu_path].append(_auto_increment_menu_idx)
else:
parent_popup_menu.add_separator()
else:
assert(false, "错误的数据类型:" + str(typeof(menu_data)) )
else:
# 根菜单按钮
for menu_name in menu_data:
# 添加菜单按钮
var menu := PopupMenu.new()
menu.name = menu_name
add_child(menu)
# 设置属性
var menu_path = parent_menu_path.path_join(menu_name)
_set_popup_menu(menu_path, menu)
# 添加这个按钮菜单的子菜单
add_menu(menu_data[menu_name], menu_path)
## 移除菜单
func remove_menu(menu_path: StringName) -> bool:
if has_menu_path(menu_path):
# 移除子菜单及其数据
var child_menu_idx_list = _child_menu_path_idx_list.get(menu_path, [])
for child_menu_idx in child_menu_idx_list:
var child_menu_path = get_menu_idx(child_menu_idx)
if has_menu_path(child_menu_path):
remove_menu(child_menu_path)
# 移除自身数据
var idx = get_menu_idx(menu_path)
# 移除菜单节点
var menu = get_menu(menu_path) as PopupMenu
if menu != null:
menu.queue_free()
else:
var parent_menu_path = get_parent_menu_path(menu_path)
var parent_menu = get_menu(parent_menu_path)
parent_menu.remove_item(idx)
_menu_path_to_popup_menu_map.erase(menu_path)
_menu_path_to_idx_map.erase(menu_path)
_idx_to_menu_path_map.erase(idx)
return true
return false
## 清空菜单
func clear_menu(menu_path: StringName) -> bool:
if has_menu_path(menu_path):
var menu = get_menu(menu_path)
if menu != null:
menu.clear()
return true
return false
## 创建菜单
##[br]
##[br][code]menu_path[/code] 菜单路径
##[br][code]parent_menu[/code] 父级菜单
func create_menu(menu_path: StringName, parent_menu: PopupMenu):
# 切分菜单名
var parent_menu_names := menu_path.split("/")
# 因为切分后 0 索引都是空字符串,所以移除
parent_menu_names.remove_at(0)
# 逐个添加菜单
parent_menu = get_menu("/" + "/".join(parent_menu_names.slice(0, 1)))
for i in parent_menu_names.size():
var sub_menu_path = "/" + "/".join(parent_menu_names.slice(0, i + 1))
# 没有这个菜单则添加
if not _menu_path_to_popup_menu_map.has(sub_menu_path):
var menu_name = parent_menu_names[i]
var menu_popup = _create_popup_menu(sub_menu_path)
_menu_path_to_popup_menu_map[sub_menu_path] = menu_popup
parent_menu.add_child(menu_popup)
parent_menu.add_submenu_item( menu_name, menu_name )
# 开始记录这个菜单,用以这个菜单的下一级别的菜单
parent_menu = get_menu(sub_menu_path)
#=====================================================
# 连接信号
#=====================================================
# 创建这个路径的菜单
func _create_popup_menu(path: StringName) -> PopupMenu:
var menu_popup = PopupMenu.new()
menu_popup.name = path.get_file()
_set_popup_menu(path, menu_popup)
return menu_popup
# 设置菜单属性
func _set_popup_menu(menu_path: StringName, menu_popup: PopupMenu):
self._menu_path_to_popup_menu_map[menu_path] = menu_popup
# 点击菜单时
menu_popup.id_pressed.connect(func(id):
self.menu_pressed.emit(id, _idx_to_menu_path_map[id])
)

View File

@ -1,456 +0,0 @@
#============================================================
# Json Editor
#============================================================
# - datetime: 2022-11-27 01:31:14
#============================================================
## JSON 数据编辑器
##
##这里将各独立的组件组合起来,功能整合
@tool
class_name TableDataEditor
extends MarginContainer
## 没有保存文件时的颜色
const NOT_SAVED_COLOR = Color(1, 0.65, 0.275, 1)
## 保存文件后的颜色
const SAVED_COLOR = Color(1, 1, 1, 0.625)
## 最大显示的最近打开的文件数量
const RECENTLY_OPEND_MAX_COUNT = 10
## 文件弹窗文件过滤
const FILTERS = ["*.gdata; GData"]
## 菜单项数据
const MENU_ITEM : Dictionary = {
"File": [
"New", "Open", {"Recently Opened": ["/"]}, "-",
"Save", "Save As...", "-",
"Export...",
"Import...",
],
"Edit": [
"Undo", "Redo", "-",
"Double click edit"
],
"Help": ["Help"],
}
## 菜单快捷键
const MENU_SHORTCUT : Dictionary = {
"/File/New": { "keycode": KEY_N, "ctrl": true },
"/File/Open": { "keycode": KEY_O, "ctrl": true },
"/File/Save": { "keycode": KEY_S, "ctrl": true },
"/File/Save As...": { "keycode": KEY_S, "ctrl": true, "shift": true },
"/File/Export...": { "keycode": KEY_E, "ctrl": true },
"/File/Import...": { "keycode": KEY_I, "ctrl": true },
"/Edit/Undo": {"keycode": KEY_Z, "ctrl": true},
"/Edit/Redo": {"keycode": KEY_Z, "ctrl": true, "shift": true},
}
const MENU_CHECKABLE : Array = [
"/Edit/Double click edit",
]
## 创建新的文件
signal created_file(path: String)
# 保存到的文件路径
var _saved_path : String = "" :
set(v):
_saved_path = v
_file_path_label.text = _saved_path
# 是否已保存
var _saved: bool = true:
set(v):
_saved = v
if _saved_status_label == null:
await ready
if _saved:
_saved_status_label.text = "(saved)"
_saved_status_label.self_modulate = SAVED_COLOR
else:
_saved_status_label.text = "(unsaved)"
_saved_status_label.self_modulate = NOT_SAVED_COLOR
# 行映射。记录哪些行有数据
var _has_value_row_map := {}
# 列映射。记录哪些列有数据
var _has_value_column_map := {}
# 撤销重做
var _undo_redo : UndoRedo = UndoRedo.new()
# 上次打开的文件路径
var _dialog_path : String = ""
# 是否已加载完成
var _is_reloaded := false :
set(v):
if _is_reloaded == false:
_is_reloaded = v
var __init_node = InjectUtil.auto_inject(self, "_")
var _table_edit : TableEdit # 编辑表格节点
var _menu_list : MenuList # 菜单列表
var _scroll_pos : LineEdit # 滚动条位置输入框
var _pages : ItemList # 切换页面(暂未开始实现功能)
var _export_preview_window : ExportPreviewWindow
var _confirm_dialog : ConfirmationDialog
var _tooltip_dialog : AcceptDialog
var _save_as_dialog : FileDialog
var _open_file_dialog : FileDialog
var _import_dialog : FileDialog
var _saved_status_label : Label
var _file_path_label : Label
var _prompt_message : Label
var _prompt_message_player : AnimationPlayer
var file_data := TableDataEditor_FileData.new({})
var cache_data := TableDataEditor_CacheData.instance()
#============================================================
# SetGet
#============================================================
## 获取编辑表格对象
func get_table_edit() -> TableEdit:
return _table_edit
#============================================================
# 内置
#============================================================
func _ready() -> void:
file_data = TableDataEditor_FileData.new({})
cache_data = TableDataEditor_CacheData.instance()
(func():
_saved_path = ""
_init_dialog()
_init_menu()
_load_last_cache_data()
_is_reloaded = true
).call_deferred()
func _exit_tree():
if not Engine.is_editor_hint() or TableDataUtil.Editor.is_enabled():
cache_data.save_data()
#============================================================
# 私有方法
#============================================================
# 新建文件
func _new_file() -> void:
load_file_path("")
# 初始化菜单列表
func _init_menu():
# TODO: 最近打开的文件替换增加数据
_menu_list.init_menu(MENU_ITEM)
# 设置快捷键
_menu_list.init_shortcut(MENU_SHORTCUT)
_menu_list.set_menu_disabled_by_path("/Edit/Undo", true)
_menu_list.set_menu_disabled_by_path("/Edit/Redo", true)
for menu_path in MENU_CHECKABLE:
_menu_list.set_menu_as_checkable(menu_path, true)
_menu_list.set_menu_check_by_path("/Edit/Double click edit", true)
# 初始化弹窗
func _init_dialog():
# 数据导出预览
_export_preview_window.close_requested.connect( func(): _export_preview_window.visible = false )
# 添加文件类型var FILTERS = ["*.gdata; GData"]
_open_file_dialog.filters = FILTERS
_save_as_dialog.filters = FILTERS
_import_dialog.filters = ["*.csv; CSV"]
# 打开窗口的路径位置
var callable = func(dialog: FileDialog):
if dialog.current_dir != _dialog_path:
_dialog_path = dialog.current_dir
_open_file_dialog.visibility_changed.connect(callable.bind(_open_file_dialog))
_save_as_dialog.visibility_changed.connect(callable.bind(_save_as_dialog))
# 加载上次缓存的数据
func _load_last_cache_data():
for dialog in [_open_file_dialog, _save_as_dialog, _import_dialog]:
dialog.current_dir = cache_data.dialog_path
dialog.visibility_changed.connect(func():
if not dialog.visible:
cache_data.dialog_path = dialog.current_dir
)
if cache_data.exists_opened_path():
load_file_path(cache_data.last_operation_path)
# 添加打开过的路径
const RECENTLY_OPEND_MENU = "/File/Recently Opened"
var list : Array = cache_data.get_recently_opend_paths()
list.reverse()
for idx in range(min(list.size(), RECENTLY_OPEND_MAX_COUNT)):
var path = list[idx]
if FileAccess.file_exists(path):
_menu_list.add_menu(path, RECENTLY_OPEND_MENU)
#============================================================
# 自定义
#============================================================
## 显示提示信息
func display_prompt_message(message: String, color: Color = Color.WHITE) -> void:
_prompt_message.text = message
_prompt_message.modulate = color
_prompt_message_player.stop()
_prompt_message_player.play("flicker")
## 加载路径的数据
##[br]
##[br][code]path[/code] 加载这个路径的数据,如果为空字符串,则是为临时数据,保存时会弹窗保存位置
func load_file_path(path: String):
# 这个文件的数据
load_file_data(TableDataEditor_FileData.load_file(path))
_saved_path = path
cache_data.update_last_operation_path(_saved_path)
cache_data.save_data()
## 加载文件数据
##[br]
##[br][code]file_data[/code] 加载文件数据
func load_file_data(file_data: TableDataEditor_FileData):
self.file_data = file_data
# 加载到表格中
(func():
_table_edit.row_to_height_map = file_data.row_height
_table_edit.column_to_width_map = file_data.column_width
_table_edit.data_set = file_data.data_set
_table_edit.get_edit_dialog().box_size = file_data.edit_dialog_size
_table_edit.update_cell_list()
).call_deferred()
# 其他
_saved = true
_saved_path = ""
_undo_redo.clear_history()
_menu_list.set_menu_disabled_by_path("/Edit/Undo", true)
_menu_list.set_menu_disabled_by_path("/Edit/Redo", true)
_table_edit.get_edit_dialog().showed = false
## 保存数据到这个路径中
func save_data_to(path: String):
if file_data.save_data(path):
# 保存成功,则进行处理
self._saved = true
self._saved_path = path
cache_data.update_last_operation_path(path)
cache_data.save_data()
self.created_file.emit(path)
display_prompt_message("保存成功. %s" % [Time.get_datetime_string_from_system().replace("T", " ")])
print("[ TableDataEditor ] 保存成功 ", Time.get_datetime_string_from_system())
else:
display_prompt_message("保存失败")
printerr("[ TableDataEditor ] 保存失败")
## 保存为 JSON
func save_as_json(path: String):
var data = _table_edit.data_set.get_origin_data()
TableDataUtil.Files.save_as_string(path, data)
self.created_file.emit(path)
## 显示保存 Dialog
func show_save_dialog(default_file_name: String = ""):
if default_file_name != "":
_save_as_dialog.current_file = default_file_name
_save_as_dialog.popup_centered_ratio(0.5)
## 导入文件
func import_file(path: String):
const FILE_TYPE = ["csv"]
if not path.get_extension() in FILE_TYPE:
display_prompt_message("错误的文件类型:%s。暂不支持 %s 以外的文件类型" % [
path.get_extension(), FILE_TYPE
])
assert(false, "错误的文件类型")
# 加载 csv数据 到 数据集 中
var data_set = TableDataEditor_TableDataSet.new()
var csv_lines = TableDataUtil.Files.read_csv_file(path)
var line : PackedStringArray
for row in csv_lines.size():
line = csv_lines[row]
for column in line.size():
data_set.set_value(Vector2i(column, row) + Vector2i.ONE, line[column])
# 加载数据
var tmp_file_data = file_data.load_file("")
tmp_file_data.data_set = data_set
load_file_data(tmp_file_data)
cache_data.dialog_path = path
cache_data.save_data()
#============================================================
# 连接信号
#============================================================
func _on_table_edit_cell_value_changed(cell: InputCell, coords: Vector2i, previous: String, value: String):
_saved = false
# print("[ TableDataEditor ] 单元格发生改变")
# 记录存在有数据的行列
_undo_redo.create_action("修改单元格的值")
_undo_redo.add_do_method( _table_edit.alter_value.bind(coords, value, false) )
_undo_redo.add_do_method( _table_edit.update_cell_list )
_undo_redo.add_undo_method( _table_edit.alter_value.bind(coords, previous, false) )
_undo_redo.add_undo_method( _table_edit.update_cell_list )
_undo_redo.commit_action()
# 撤销可用性
_menu_list.set_menu_disabled_by_path("/Edit/Undo", false)
func _on_table_edit_scroll_changed(coords: Vector2i):
_scroll_pos.text = str(coords)
func _on_scroll_pos_text_submitted(new_text):
var re = RegEx.new()
re.compile("(\\d+)\\s*,\\s*(\\d+)")
var result = re.search(new_text)
if result == null:
return
var pos = str_to_var("Vector2i(%s, %s)" % [result.get_string(1), result.get_string(2)])
if pos is Vector2i:
_table_edit.scroll_to(pos)
print("[ TableDataEditor ] 跳转到位置:", pos)
func _on_menu_list_menu_pressed(idx, menu_path: StringName):
# print_debug("[ TableDataEditor ] 点击菜单 ", menu_path)
match menu_path:
"/File/New":
if not _saved:
_confirm_dialog.dialog_text = "当前还没有保存,是否要继续创建?"
_confirm_dialog.popup_centered()
else:
_new_file()
"/File/Open":
_open_file_dialog.popup_centered_ratio(0.5)
"/File/Save":
if _saved_path == "":
show_save_dialog()
else:
save_data_to(_saved_path)
"/File/Save As...":
show_save_dialog("new_file.gdata")
"/File/Export...":
_export_preview_window.popup_centered_ratio(0.5)
_export_preview_window.update_text_box_content()
"/File/Import...":
_import_dialog.popup_centered_ratio(0.5)
"/Edit/Undo":
_undo_redo.undo()
_menu_list.set_menu_disabled_by_path("/Edit/Undo", not _undo_redo.has_undo())
_menu_list.set_menu_disabled_by_path("/Edit/Redo", false)
"/Edit/Redo":
_undo_redo.redo()
_menu_list.set_menu_disabled_by_path("/Edit/Redo", not _undo_redo.has_redo())
_menu_list.set_menu_disabled_by_path("/Edit/Undo", false)
"/Edit/Double click edit":
var status = _menu_list.get_menu_check_by_path("/Edit/Double click edit")
_menu_list.set_menu_check_by_path("/Edit/Double click edit", not status)
_table_edit.double_click_edit = not status
"/Help/Help":
_tooltip_dialog.popup_centered()
_:
# 最近打开的文件
if menu_path.contains("/File/Recently Opened"):
var file_path = menu_path.trim_prefix("/File/Recently Opened/")
if FileAccess.file_exists(file_path):
load_file_path(file_path)
else:
printerr("文件不存在")
_menu_list.remove_menu(menu_path)
func _on_save_as_dialog_file_selected(path):
_saved_path = path
match _saved_path.get_extension():
"gdata":
save_data_to( _saved_path )
"json":
save_as_json( _saved_path )
_:
display_prompt_message("错误的文件类型:%s" % [ _saved_path.get_extension() ])
printerr("[ TableDataEditor ] <Unknown Type> ", _saved_path.get_extension())
func _on_export_preview_window_exported(path, type, data):
_export_preview_window.visible = false
display_prompt_message("已导出 %s 资源" % [type])
self.created_file.emit(path)
func _on_open_file_dialog_file_selected(path: String):
cache_data.dialog_path = path.get_base_dir()
load_file_path(path)
func _on_file_path_label_gui_input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT and event.double_click:
if _saved_path != "" and DirAccess.dir_exists_absolute(_saved_path.get_base_dir()):
var path = TableDataUtil.Files.get_absolute_path(_saved_path)
OS.shell_open(path.get_base_dir())
func _on_table_edit_popup_edit_box_size_changed(box_size):
file_data.edit_dialog_size = box_size

View File

@ -1,233 +0,0 @@
[gd_scene load_steps=8 format=3 uid="uid://68vjwfxquvlf"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_data_editor.gd" id="1_j3oce"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/menu_list.gd" id="2_o7gjv"]
[ext_resource type="PackedScene" uid="uid://ctppgkl2dpksd" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_edit.tscn" id="3_87xax"]
[ext_resource type="PackedScene" uid="uid://cqpmbxqgny2kq" path="res://addons/table_data_editor/src/table_data_editor/export_preview/export_preview_window.tscn" id="4_8p413"]
[sub_resource type="Animation" id="Animation_jmtv6"]
length = 0.001
[sub_resource type="Animation" id="Animation_lfmqt"]
resource_name = "flicker"
length = 8.0
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:modulate:a")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 5, 8),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [0.0, 0.55, 1.0, 0.55, 1.0, 0.55, 1.0, 0.55, 1.0, 1.0, 0.0]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_xid6c"]
_data = {
"RESET": SubResource("Animation_jmtv6"),
"flicker": SubResource("Animation_lfmqt")
}
[node name="table_data_editor" type="MarginContainer"]
clip_contents = true
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 2
script = ExtResource("1_j3oce")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="menu_list" type="MenuBar" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 1
flat = true
script = ExtResource("2_o7gjv")
[node name="GridContainer" type="HSplitContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
split_offset = 120
[node name="pages" type="ItemList" parent="VBoxContainer/GridContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="table_edit" parent="VBoxContainer/GridContainer" instance=ExtResource("3_87xax")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/PanelContainer"]
layout_mode = 2
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer/MarginContainer"]
layout_mode = 2
[node name="scroll_pos" type="LineEdit" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
focus_mode = 1
text = "(1, 1)"
alignment = 1
select_all_on_focus = true
[node name="MarginContainer3" type="MarginContainer" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 4
[node name="Panel" type="Panel" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/MarginContainer3"]
custom_minimum_size = Vector2(1, 0)
layout_mode = 2
[node name="Control" type="HBoxContainer" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 1
[node name="prompt_message" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/Control"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0)
layout_mode = 2
text = "(Prompt Message)"
[node name="prompt_message_player" type="AnimationPlayer" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/Control/prompt_message"]
unique_name_in_owner = true
libraries = {
"": SubResource("AnimationLibrary_xid6c")
}
[node name="MarginContainer2" type="MarginContainer" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 4
[node name="Panel" type="Panel" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/MarginContainer2"]
custom_minimum_size = Vector2(1, 0)
layout_mode = 2
[node name="file_path_label" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
tooltip_text = "双击打开所在文件目录"
focus_mode = 1
mouse_filter = 0
text = "res://addons/table_data_editor/column_width_test.gdata"
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 4
[node name="Panel" type="Panel" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/MarginContainer"]
custom_minimum_size = Vector2(1, 0)
layout_mode = 2
[node name="saved_status_label" type="Label" parent="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer"]
unique_name_in_owner = true
self_modulate = Color(1, 1, 1, 0.625)
layout_mode = 2
size_flags_horizontal = 8
focus_mode = 1
text = "(saved)"
[node name="export_preview_window" parent="." instance=ExtResource("4_8p413")]
unique_name_in_owner = true
position = Vector2i(0, -500)
visible = false
_table_data_editor = NodePath("..")
[node name="confirm_dialog" type="ConfirmationDialog" parent="."]
unique_name_in_owner = true
[node name="tooltip_dialog" type="AcceptDialog" parent="."]
unique_name_in_owner = true
gui_embed_subwindows = true
title = "Help"
size = Vector2i(500, 317)
[node name="MarginContainer" type="MarginContainer" parent="tooltip_dialog"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Label" type="Label" parent="tooltip_dialog/MarginContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "双击单元格进行编辑
按住 Alt 键进行左右滚动,松开即可上下滚动
按住 Tab 键或 Enter 键进行切换到下一个编辑的单元格,如果同时按下 Shift 则是切换到上一个单元格
"
[node name="save_as_dialog" type="FileDialog" parent="."]
unique_name_in_owner = true
filters = PackedStringArray("*.gdata; GData")
[node name="open_file_dialog" type="FileDialog" parent="."]
unique_name_in_owner = true
title = "Open a File"
size = Vector2i(312, 157)
ok_button_text = "打开"
file_mode = 0
filters = PackedStringArray("*.gdata; GData")
[node name="import_dialog" type="FileDialog" parent="."]
unique_name_in_owner = true
title = "Open a File"
size = Vector2i(295, 161)
ok_button_text = "打开"
file_mode = 0
access = 2
filters = PackedStringArray("*.csv; CSV")
[connection signal="menu_pressed" from="VBoxContainer/menu_list" to="." method="_on_menu_list_menu_pressed"]
[connection signal="cell_value_changed" from="VBoxContainer/GridContainer/table_edit" to="." method="_on_table_edit_cell_value_changed"]
[connection signal="popup_edit_box_size_changed" from="VBoxContainer/GridContainer/table_edit" to="." method="_on_table_edit_popup_edit_box_size_changed"]
[connection signal="scroll_changed" from="VBoxContainer/GridContainer/table_edit" to="." method="_on_table_edit_scroll_changed"]
[connection signal="text_submitted" from="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/scroll_pos" to="." method="_on_scroll_pos_text_submitted"]
[connection signal="gui_input" from="VBoxContainer/PanelContainer/MarginContainer/HBoxContainer/file_path_label" to="." method="_on_file_path_label_gui_input"]
[connection signal="exported" from="export_preview_window" to="." method="_on_export_preview_window_exported"]
[connection signal="confirmed" from="confirm_dialog" to="." method="_new_file"]
[connection signal="file_selected" from="save_as_dialog" to="." method="_on_save_as_dialog_file_selected"]
[connection signal="file_selected" from="open_file_dialog" to="." method="_on_open_file_dialog_file_selected"]
[connection signal="file_selected" from="import_dialog" to="." method="import_file"]

View File

@ -1,13 +0,0 @@
#============================================================
# Base Cell Element
#============================================================
# - datetime: 2022-11-26 22:25:48
#============================================================
## 基础的单元格元素
@tool
class_name BaseCellElement
extends MarginContainer

View File

@ -1,11 +0,0 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/base_cell_element.gd" id="1_wjxc7"]
[node name="base_cell_element" type="MarginContainer"]
offset_right = 80.0
offset_bottom = 32.0
focus_mode = 2
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
script = ExtResource("1_wjxc7")

View File

@ -1,145 +0,0 @@
#============================================================
# Input Cell
#============================================================
# - datetime: 2022-11-26 18:12:34
#============================================================
##输入单元格
@tool
class_name InputCell
extends MarginContainer
##单击单元格
signal single_clicked
##双击单元格
signal double_clicked
##水平方向拖拽
##[br]
##[br][code]distance[/code] 为当前鼠标位置与第一次按下时鼠标位置的距离
##[br][code]pressed_size[/code] 为点击时的节点的大小
signal h_dragged(distance: float, pressed_node_size: Vector2i)
##垂直方向拖拽
##[br]
##[br][code]distance[/code] 为当前鼠标位置与第一次按下时鼠标位置的距离
##[br][code]pressed_size[/code] 为点击时的节点的大小
signal v_dragged(distance: float, pressed_node_size: Vector2i)
var _data : String
var _latest_v_dragged := false
var _latest_h_dragged := false
var _latest_dragged := false
# 点击时的鼠标位置
var _pressed_mouse_pos: Vector2
# 点击时节点的大小
var _pressed_node_size: Vector2i
# 是否可以拖拽,防止 _event 发送速度过快
var _enabled_dragged := true
@onready
var _text_edit := %text_edit as TextEdit
@onready
var _process_timer := %process_timer as Timer
#============================================================
# SetGet
#============================================================
func set_value(v: String):
_data = v
show_value(v)
func get_value() -> String:
return _data
func show_value(v):
_text_edit.text = v if v else ""
## 值发生了改变
func is_changed():
return _text_edit.text != _data
#============================================================
# 内置
#============================================================
func _ready():
# _text_edit.gui_input.connect(self._gui_input)
_text_edit.focus_entered.connect( func(): self.focus_entered.emit() )
_text_edit.focus_exited.connect( func():
self.focus_exited.emit()
if is_changed():
set_value(_text_edit.text)
)
set_process(false)
_process_timer.timeout.connect( set_process.bind(false) )
self.mouse_entered.connect(func():
set_process(true)
_process_timer.stop()
)
self.mouse_exited.connect(func():
_process_timer.start()
)
func _process(delta):
# 更新显示的鼠标图像
var margin = custom_minimum_size - get_local_mouse_position()
if margin.x < self["theme_override_constants/margin_right"] and margin.y < self["theme_override_constants/margin_bottom"]:
self.mouse_default_cursor_shape = Control.CURSOR_MOVE
elif margin.x <= self["theme_override_constants/margin_right"]:
self.mouse_default_cursor_shape = Control.CURSOR_HSPLIT
elif margin.y <= self["theme_override_constants/margin_bottom"]:
self.mouse_default_cursor_shape = Control.CURSOR_VSPLIT
else:
if not(_latest_h_dragged or _latest_v_dragged):
self.mouse_default_cursor_shape = Control.CURSOR_ARROW
_enabled_dragged = true
func _gui_input(event):
if event is InputEventMouseMotion:
if _enabled_dragged:
var diff = get_local_mouse_position() - _pressed_mouse_pos
if _latest_h_dragged:
self.custom_minimum_size.x = _pressed_node_size.x + diff.x
h_dragged.emit(self.custom_minimum_size.x - _pressed_node_size.x, _pressed_node_size)
_process_timer.stop()
if _latest_v_dragged:
self.custom_minimum_size.y = _pressed_node_size.y + diff.y
v_dragged.emit(self.custom_minimum_size.y - _pressed_node_size.y, _pressed_node_size)
_process_timer.stop()
_enabled_dragged = false
elif event is InputEventMouseButton:
if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
var margin = custom_minimum_size - get_local_mouse_position()
if event.double_click:
self.double_clicked.emit()
else:
_pressed_node_size = self.size
_pressed_mouse_pos = get_local_mouse_position()
if margin.x <= self["theme_override_constants/margin_right"]:
_latest_h_dragged = true
if margin.y <= self["theme_override_constants/margin_bottom"]:
_latest_v_dragged = true
self.single_clicked.emit()
else:
self.mouse_default_cursor_shape = Control.CURSOR_ARROW
_latest_h_dragged = false
_latest_v_dragged = false
var diff = self.custom_minimum_size - get_local_mouse_position()
if diff.x < 0 or diff.y < 0:
_process_timer.start()

View File

@ -1,124 +0,0 @@
[gd_scene load_steps=16 format=3]
[ext_resource type="PackedScene" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/base_cell_element.tscn" id="1_ney3p"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/input_cell/input_cell.gd" id="2_y6m17"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_54uk8"]
[sub_resource type="Image" id="Image_pbgwo"]
data = {
"data": PackedByteArray(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, 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, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 3, 255, 255, 255, 41, 255, 255, 255, 67, 255, 255, 255, 67, 255, 255, 255, 40, 255, 255, 255, 3, 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, 41, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 40, 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, 67, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 67, 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, 67, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 67, 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, 40, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 74, 255, 255, 255, 40, 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, 3, 255, 255, 255, 40, 255, 255, 255, 67, 255, 255, 255, 67, 255, 255, 255, 40, 255, 255, 255, 3, 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, 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, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[sub_resource type="ImageTexture" id="ImageTexture_o2enw"]
image = SubResource("Image_pbgwo")
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_hq7i2"]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
texture = SubResource("ImageTexture_o2enw")
margin_left = 6.0
margin_top = 6.0
margin_right = 6.0
margin_bottom = 6.0
region_rect = Rect2(0, 0, 12, 12)
[sub_resource type="Image" id="Image_s7tle"]
data = {
"data": PackedByteArray(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, 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, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 6, 248, 248, 248, 102, 249, 249, 249, 168, 249, 249, 249, 168, 248, 248, 248, 101, 213, 213, 213, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 248, 248, 248, 102, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 248, 248, 248, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 249, 249, 249, 168, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 249, 249, 249, 168, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 248, 248, 248, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 248, 248, 248, 101, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 249, 249, 249, 186, 250, 250, 250, 99, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 213, 213, 213, 6, 248, 248, 248, 101, 249, 249, 249, 168, 248, 248, 248, 168, 250, 250, 250, 99, 213, 213, 213, 6, 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, 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, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[sub_resource type="ImageTexture" id="ImageTexture_pi2w1"]
image = SubResource("Image_s7tle")
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_daefy"]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
texture = SubResource("ImageTexture_pi2w1")
margin_left = 5.0
margin_top = 5.0
margin_right = 5.0
margin_bottom = 5.0
region_rect = Rect2(0, 0, 12, 12)
[sub_resource type="Image" id="Image_4ojul"]
data = {
"data": PackedByteArray(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, 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, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 213, 213, 213, 6, 180, 180, 180, 102, 181, 181, 181, 168, 181, 181, 181, 168, 179, 179, 179, 101, 170, 170, 170, 6, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 180, 180, 180, 102, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 179, 179, 179, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 181, 181, 181, 168, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 181, 181, 181, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 181, 181, 181, 168, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 179, 179, 179, 168, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 179, 179, 179, 101, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 180, 180, 180, 186, 181, 181, 181, 99, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 170, 170, 170, 6, 179, 179, 179, 101, 181, 181, 181, 168, 179, 179, 179, 168, 181, 181, 181, 99, 170, 170, 170, 6, 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, 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, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 12,
"mipmaps": false,
"width": 12
}
[sub_resource type="ImageTexture" id="ImageTexture_tvj35"]
image = SubResource("Image_4ojul")
[sub_resource type="StyleBoxTexture" id="StyleBoxTexture_x7jas"]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
texture = SubResource("ImageTexture_tvj35")
margin_left = 6.0
margin_top = 6.0
margin_right = 6.0
margin_bottom = 6.0
region_rect = Rect2(0, 0, 12, 12)
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_eycgy"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_64aem"]
[sub_resource type="Theme" id="Theme_cy8yd"]
HScrollBar/icons/decrement = null
HScrollBar/icons/decrement_highlight = null
HScrollBar/icons/decrement_pressed = null
HScrollBar/icons/increment = null
HScrollBar/icons/increment_highlight = null
HScrollBar/icons/increment_pressed = null
HScrollBar/styles/grabber = null
HScrollBar/styles/grabber_highlight = null
HScrollBar/styles/grabber_pressed = null
HScrollBar/styles/scroll = SubResource("StyleBoxEmpty_54uk8")
HScrollBar/styles/scroll_focus = null
VScrollBar/icons/decrement = null
VScrollBar/icons/decrement_highlight = null
VScrollBar/icons/decrement_pressed = null
VScrollBar/icons/increment = null
VScrollBar/icons/increment_highlight = null
VScrollBar/icons/increment_pressed = null
VScrollBar/styles/grabber = SubResource("StyleBoxTexture_hq7i2")
VScrollBar/styles/grabber_highlight = SubResource("StyleBoxTexture_daefy")
VScrollBar/styles/grabber_pressed = SubResource("StyleBoxTexture_x7jas")
VScrollBar/styles/scroll = SubResource("StyleBoxEmpty_eycgy")
VScrollBar/styles/scroll_focus = SubResource("StyleBoxEmpty_64aem")
[node name="input_cell" instance=ExtResource("1_ney3p")]
custom_minimum_size = Vector2(80, 35)
offset_bottom = 35.0
mouse_filter = 0
script = ExtResource("2_y6m17")
[node name="text_edit" type="TextEdit" parent="." index="0"]
unique_name_in_owner = true
offset_right = 76.0
offset_bottom = 31.0
mouse_filter = 2
theme = SubResource("Theme_cy8yd")
[node name="process_timer" type="Timer" parent="." index="1"]
unique_name_in_owner = true
wait_time = 2.0
one_shot = true

View File

@ -1,19 +0,0 @@
#============================================================
# Serial Number Cell
#============================================================
# - datetime: 2022-11-26 22:33:13
#============================================================
# 序列号表格
@tool
class_name SerialNumberCell
extends BaseCellElement
@onready
var label := $label as Label
func show_number(v: int):
if label == null: await ready
label.text = str(v)

View File

@ -1,17 +0,0 @@
[gd_scene load_steps=3 format=3]
[ext_resource type="PackedScene" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/base_cell_element.tscn" id="1_5yxaj"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/serial_number_cell/serial_number_cell.gd" id="2_bf00s"]
[node name="serial_number_cell" instance=ExtResource("1_5yxaj")]
offset_right = 50.0
offset_bottom = 30.0
theme_override_constants/margin_right = 8
script = ExtResource("2_bf00s")
[node name="label" type="Label" parent="." index="0"]
offset_right = 42.0
offset_bottom = 26.0
size_flags_vertical = 1
text = "0"
vertical_alignment = 1

View File

@ -1,136 +0,0 @@
#============================================================
# Edit Box Window
#============================================================
# - author: zhangxuetu
# - datetime: 2023-03-19 11:48:30
# - version: 4.0
#============================================================
@tool
class_name PopupEditBox
extends Control
signal popup_hide(text: String)
signal box_size_changed(box_size: Vector2)
signal input_switch_char(character: int)
@export
var text : String = "" :
set(v):
text = v
if not is_inside_tree(): await ready
if _edit_box.text != text:
_edit_box.text = text
@export
var showed : bool = true:
set(v):
if v != self.visible:
showed = v
self.visible = v
@export
var box_size: Vector2 :
set(v):
box_size = v
if not is_inside_tree(): await ready
if _edit_box.size != box_size:
_edit_box.size = box_size
get:
if _edit_box:
return _edit_box.size
return Vector2(0, 0)
@onready var _edit_box := %edit_box as TextEdit
@onready var _scale_rect := %scale_rect as Control
var _resize_pressed : bool = false
var _pressed_size : Vector2 = Vector2(0,0)
var _pressed_pos : Vector2 = Vector2(0,0)
#============================================================
# SetGet
#============================================================
func get_edit_box() -> TextEdit:
return _edit_box
func get_text() -> String:
return _edit_box.text
#============================================================
# 内置
#============================================================
func _ready():
_edit_box.position = Vector2(0,0)
_scale_rect.gui_input.connect(func(event):
if event is InputEventMouseMotion:
if _resize_pressed:
var diff_v = get_global_mouse_position() - _pressed_pos
_edit_box.size = _pressed_size + diff_v
elif event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
_resize_pressed = event.pressed
if _resize_pressed:
_pressed_size = _edit_box.size
_pressed_pos = get_global_mouse_position()
)
#============================================================
# 自定义
#============================================================
func popup(rect: Rect2 = Rect2()):
if _edit_box == null: await ready
if rect.position != Vector2():
_edit_box.global_position = rect.position
if rect.size != Vector2():
_edit_box.size = rect.size
# 聚焦编辑
_edit_box.visible = true
_edit_box.set_caret_line( _edit_box.get_line_count() )
_edit_box.set_caret_column( _edit_box.text.length() )
self.showed = true
# print("[ PopupEditBox ] 弹出窗口")
# 取消焦点时隐藏
var t = _edit_box.text
_edit_box.grab_focus()
_edit_box.focus_exited.connect(func():
if t != _edit_box.text:
self.popup_hide.emit(_edit_box.text)
_edit_box.visible = false
# print("[ PopupEditBox ] 弹窗隐藏")
, Object.CONNECT_ONE_SHOT)
func _on_edit_box_resized():
if _edit_box == null: await ready
self.box_size_changed.emit(_edit_box.size)
func _on_edit_box_gui_input(event):
if event is InputEventKey:
if event.pressed:
if not event.alt_pressed:
# Enter/Tab 切换单元格
if event.keycode in [KEY_ENTER, KEY_KP_ENTER]:
self.input_switch_char.emit(KEY_ENTER)
get_tree().root.set_input_as_handled()
elif event.keycode in [KEY_TAB]:
self.input_switch_char.emit(KEY_TAB)
get_tree().root.set_input_as_handled()
else:
# Alt+Enter换行
if event.keycode in [KEY_ENTER, KEY_KP_ENTER]:
_edit_box.insert_text_at_caret("\n")

View File

@ -1,42 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://4xts0ha85fja"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/edit_box_window/edit_box_window.gd" id="1_qj0ea"]
[node name="popup_edit_box" type="Control"]
visible = false
layout_mode = 3
anchors_preset = 0
size_flags_horizontal = 0
size_flags_vertical = 0
mouse_filter = 2
script = ExtResource("1_qj0ea")
box_size = Vector2(200, 120)
[node name="edit_box" type="TextEdit" parent="."]
unique_name_in_owner = true
custom_minimum_size = Vector2(100, 30)
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 200.0
offset_bottom = 120.0
grow_horizontal = 2
grow_vertical = 2
wrap_mode = 1
[node name="scale_rect" type="Control" parent="edit_box"]
unique_name_in_owner = true
layout_mode = 1
anchor_left = 0.996
anchor_top = 0.987
anchor_right = 0.996
anchor_bottom = 0.987
offset_left = -0.200012
offset_top = -0.440002
offset_right = 7.79999
offset_bottom = 7.56
mouse_default_cursor_shape = 12
[connection signal="gui_input" from="edit_box" to="." method="_on_edit_box_gui_input"]
[connection signal="resized" from="edit_box" to="." method="_on_edit_box_resized"]

View File

@ -1,88 +0,0 @@
#============================================================
# Serial Number Container
#============================================================
# - datetime: 2022-11-26 23:09:40
#============================================================
# 显示左上的数字序号的容器
@tool
class_name SerialNumberContainer
extends GridContainer
## 显示序号的单元格场景
@export var serial_number_cell : PackedScene
var __init_node = InjectUtil.auto_inject(self, "_", true)
var _table_container : TableContainer
var _h_serial_number_container : HBoxContainer
var _v_serial_number_container : VBoxContainer
var _space : Control
var _last_top_left : Vector2i
#============================================================
# 自定义
#============================================================
## 更新横竖列数字
##[br]
##[br][code]top_left[/code] 以 top_left 值开始向下更新
func update_serial_number(top_left: Vector2i):
var serial_number : SerialNumberCell
for i in _h_serial_number_container.get_child_count():
serial_number = _h_serial_number_container.get_child(i) as SerialNumberCell
serial_number.show_number(top_left.x + i)
for i in _v_serial_number_container.get_child_count():
serial_number = _v_serial_number_container.get_child(i) as SerialNumberCell
serial_number.show_number(top_left.y + i)
_last_top_left = top_left
_space.custom_minimum_size = Vector2(_v_serial_number_container.size.x, _h_serial_number_container.size.y)
if _space.custom_minimum_size == Vector2(0,0):
_space.custom_minimum_size = Vector2(27, 35)
## 更新这个行高
func update_row_height(origin_row: int, height: int):
if _v_serial_number_container.get_child_count() > 0:
var node = _v_serial_number_container.get_child(origin_row) as Control
node.custom_minimum_size.y = height
## 更新这个列宽
func update_column_width(origin_column: int, width: int):
if _h_serial_number_container.get_child_count() > 0:
var node = _h_serial_number_container.get_child(origin_column) as Control
node.custom_minimum_size.x = width
## 更新行列数字标题节点的数量
func update_grid_cell_count(grid_size: Vector2i):
if _table_container == null:
while _table_container == null:
await get_tree().process_frame
# 表格数量发生改变时添加序号节点单元格
var tile_size = _table_container.get_tile_size()
# print_debug("单元格大小:", tile_size)
# 水平单元格
if grid_size.x > _h_serial_number_container.get_child_count():
var diff_count = grid_size.x - _h_serial_number_container.get_child_count()
for i in diff_count:
var node = serial_number_cell.instantiate() as Control
node.custom_minimum_size = tile_size
_h_serial_number_container.add_child(node)
# 垂直单元格
if grid_size.y > _v_serial_number_container.get_child_count():
var diff_count = grid_size.y - _v_serial_number_container.get_child_count()
for i in diff_count:
var node = serial_number_cell.instantiate() as Control
node.custom_minimum_size.y = tile_size.y
_v_serial_number_container.add_child(node)
update_serial_number(_last_top_left)

View File

@ -1,39 +0,0 @@
#============================================================
# Line
#============================================================
# - datetime: 2022-11-26 16:57:14
#============================================================
@tool
class_name ColumnContainer
extends MarginContainer
signal newly_added_cell(cell: Node)
@export
var item : PackedScene
@onready
var container := %container as HBoxContainer
func update_cell_amount(count: int):
if container == null:
await self.ready
if item:
var node
for i in count - container.get_child_count():
node = item.instantiate()
container.add_child(node)
newly_added_cell.emit(node)
func get_cells():
return container.get_children()
func get_cell(idx: int) -> Node:
return container.get_child(idx)

View File

@ -1,18 +0,0 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_container/column_container/column_container.gd" id="1_1xecd"]
[node name="column_container" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_1xecd")
metadata/_edit_lock_ = true
[node name="container" type="HBoxContainer" parent="."]
unique_name_in_owner = true
offset_right = 1152.0
offset_bottom = 648.0
theme_override_constants/separation = 0

View File

@ -1,36 +0,0 @@
#============================================================
# List
#============================================================
# - datetime: 2022-11-26 16:57:09
#============================================================
## 数据行的容器
@tool
class_name RowContainer
extends MarginContainer
signal newly_added_line(line: ColumnContainer)
@export var item : PackedScene
@onready var container = %container as VBoxContainer
func get_columns_containers() -> Array:
return container.get_children()
## 更新行数量
func update_row_amount(count: int):
if container == null:
await self.ready
var node
for i in count - container.get_child_count():
node = item.instantiate()
container.add_child(node)
newly_added_line.emit(node)

View File

@ -1,20 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://dyhbjld6bhk1p"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_container/row_container/row_container.gd" id="1_hd2dr"]
[ext_resource type="PackedScene" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_container/column_container/column_container.tscn" id="2_cbcfu"]
[node name="row_container" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 1.0
offset_right = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_hd2dr")
item = ExtResource("2_cbcfu")
[node name="container" type="VBoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/separation = 0

View File

@ -1,147 +0,0 @@
#============================================================
# Table Container
#============================================================
# - datetime: 2022-11-26 17:01:54
#============================================================
## 表格
##
##这里只进行表格单元格相关管理,不进行数据的处理,仅作为表格样式显示
@tool
class_name TableContainer
extends MarginContainer
## 新增单元格
signal newly_added_cell(coords: Vector2i, new_cell: Control)
## 网格中的单元格大小发生改变
signal grid_cell_size_changed(grid_size: Vector2i)
@export var cell : PackedScene
var __init_node = InjectUtil.auto_inject(self, "_", true)
var _row_container : RowContainer
var _update_row_column_amount_timer : Timer
# 表格单元格数量大小
var _grid_cell_count_size := Vector2i()
# 单个单元格大小
var _tile_size := Vector2i()
# 单元格列表
var _cell_list : Array[Control] = []
# 列对应的单元格
var _column_to_cells_map := {}
# 行对应的单元格
var _row_to_cells_map := {}
#============================================================
# SetGet
#============================================================
## 获取单元格行列数量大小
func get_grid_row_column_count_size() -> Vector2i:
return _grid_cell_count_size
## 获取表格的行数量
func get_grid_row_count() -> int:
return _grid_cell_count_size.y
## 获取表格的列数量
func get_grid_column_count() -> int:
return _grid_cell_count_size.x
## 获取单元格大小
func get_tile_size() -> Vector2i:
return _tile_size
## 获取行容器
func get_row_container() -> RowContainer:
return _row_container
## 获取所有行的单元格列容器
func get_column_container() -> Array[ColumnContainer]:
return _row_container.get_columns_containers()
## 获取所有单元格
func get_all_cell() -> Array[Control]:
return _cell_list
## 获取一列的单元格。column 从 0 开始,最大为 [method get_grid_row_column_count_siz] 的 x 值
func get_column_cells(column: int) -> Array:
return _column_to_cells_map[column]
## 获取这一行的单元格。row 从 0 开始,最大为 [method get_grid_row_column_count_siz] 的 y 值
func get_row_cells(row: int) -> Array:
return _row_to_cells_map[row]
#============================================================
# 内置
#============================================================
func _ready() -> void:
assert(cell != null, "还没有设置 cell 属性!")
# cell 的更新与大小
_row_container.newly_added_line.connect(func(new_line: ColumnContainer):
new_line.newly_added_cell.connect( func(new_cell: Control):
self._cell_list.append(new_cell)
var column : int = new_cell.get_index()
var row : int = new_line.get_index()
self.newly_added_cell.emit( Vector2i(column, row), new_cell )
if _column_to_cells_map.has(column):
_column_to_cells_map[column].append(new_cell)
else:
_column_to_cells_map[column] = []
_column_to_cells_map[column].append(new_cell)
if _row_to_cells_map.has(row):
_row_to_cells_map[row].append(new_cell)
else:
_row_to_cells_map[row] = []
_row_to_cells_map[row].append(new_cell)
)
new_line.item = self.cell
new_line.update_cell_amount(_grid_cell_count_size.x)
)
self.resized.connect(_update_row_column_amount)
# 先创建出来第一个,用以获取最小 cell 大小
_row_container.update_row_amount(1)
(_row_container.get_columns_containers()[0] as ColumnContainer).update_cell_amount(1)
var first_cell = _row_container.get_columns_containers()[0].get_cells()[0] as Control
_tile_size = first_cell.size
# 更新单元格数量
_update_row_column_amount_timer.timeout.connect(func():
var tmp_cell_amount = _grid_cell_count_size
_grid_cell_count_size.x = int(self.size.x / _tile_size.x) + 1
_grid_cell_count_size.y = int(self.size.y / _tile_size.y) + 1
if tmp_cell_amount != _grid_cell_count_size:
print("[ TableContainer ] 单元格小:", _grid_cell_count_size)
self.grid_cell_size_changed.emit(_grid_cell_count_size)
for line in _row_container.get_columns_containers():
line = line as ColumnContainer
line.update_cell_amount(_grid_cell_count_size.x)
_row_container.update_row_amount(_grid_cell_count_size.y)
)
_update_row_column_amount_timer.start()
_update_row_column_amount_timer.autostart = true
_update_row_column_amount_timer.one_shot = true
_update_row_column_amount_timer.timeout.emit()
#============================================================
# 自定义
#============================================================
func _update_row_column_amount():
_update_row_column_amount_timer.stop()
_update_row_column_amount_timer.start()

View File

@ -1,42 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://d02f0rqt6qnpv"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_container/table_container.gd" id="1_v7wlj"]
[ext_resource type="PackedScene" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/input_cell/input_cell.tscn" id="2_t8gur"]
[ext_resource type="PackedScene" uid="uid://dyhbjld6bhk1p" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_container/row_container/row_container.tscn" id="3_auuku"]
[node name="table_container" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_v7wlj")
cell = ExtResource("2_t8gur")
metadata/_edit_lock_ = true
[node name="control" type="Control" parent="."]
unique_name_in_owner = true
clip_contents = true
layout_mode = 2
mouse_filter = 2
metadata/_edit_lock_ = true
[node name="row_container" parent="control" instance=ExtResource("3_auuku")]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 0.0
offset_right = 0.0
grow_horizontal = 1
grow_vertical = 1
size_flags_horizontal = 3
size_flags_vertical = 3
metadata/_edit_lock_ = true
[node name="update_row_column_amount_timer" type="Timer" parent="."]
unique_name_in_owner = true
wait_time = 0.05
one_shot = true
autostart = true

View File

@ -1,105 +0,0 @@
#============================================================
# Table Data Set
#============================================================
# - author: zhangxuetu
# - datetime: 2023-05-16 23:22:41
# - version: 4.0
#============================================================
## 表格数据集
##
##专门存储管理表单的数据。处理每个单元格中的数据
class_name TableDataEditor_TableDataSet
enum {
COLUMN,
ROW,
}
# 表格数据。以 [code]grid_data[行][列] = 值[/code] 的格式存储数据。
var grid_data : Dictionary = {}
# 列集合。所有行哪些列有值
var column_set : Dictionary = {}
#============================================================
# SetGet
#============================================================
func get_config_data():
return {
"grid_data": grid_data,
"column_set": column_set,
}
func set_config_data(data: Dictionary):
self.grid_data = data.get("grid_data", {})
self.column_set = data.get("column_set", {})
func get_origin_data() -> Dictionary:
return grid_data
func is_empty() -> bool:
return grid_data.is_empty()
func has_value(coords: Vector2i) -> bool:
return (grid_data.has(coords[ROW])
and grid_data[coords[ROW]].has(coords[COLUMN])
)
func get_value(coords: Vector2i):
if has_value(coords):
var row : int = coords[ROW]
var column : int = coords[COLUMN]
return grid_data[row][column]
return ""
func set_value(coords: Vector2i, value) -> void:
var row : int = coords[ROW]
var column : int = coords[COLUMN]
if not grid_data.has(row):
grid_data[row] = {}
grid_data[row][column] = value
if not column_set.has(column):
column_set[column] = 0
column_set[column] += 1
func get_max_column() -> int:
if column_set.is_empty():
return 0
return column_set.keys().max()
func remove_value(coords: Vector2i) -> bool:
if has_value(coords):
var row : int = coords[ROW]
var column : int = coords[COLUMN]
grid_data[row].erase(column)
column_set[column] -= 1
# 没有数据时,进行移除
if Dictionary(grid_data[row]).is_empty():
grid_data.erase(row)
if column_set[column] == 0:
column_set.erase(column)
return true
return false
func get_row_list() -> Array[int]:
return Array(grid_data.keys(), TYPE_INT, "", null)
#============================================================
# 内置
#============================================================
func _to_string():
return var_to_str( TableDataUtil.Classes.get_dict_by_property(self) )
func _init(data: Dictionary = {}):
self.grid_data = data.get("grid_data", {})
self.column_set = data.get("column_set", {})

View File

@ -1,498 +0,0 @@
#============================================================
# Table Edit
#============================================================
# - author: zhangxuetu
# - datetime: 2022-11-26 17:53:52
# - version: 4.0
#============================================================
## 编辑网格
##
##这里管理 Cell 单元格的数据内容,真正开始对表格数据进行处理。
@tool
class_name TableEdit
extends MarginContainer
## 数据发生改变
signal data_set_changed
## 选中单元格
signal selected_cell(cell: InputCell)
## 取消选中单元格
signal deselected_cell(cell: InputCell)
## 单击单元格
signal single_clicked_cell(cell: InputCell)
## 双击单元格
signal double_clicked_cell(cell: InputCell)
## 准备编辑单元格
signal ready_edit_cell(cell: InputCell)
## 已编辑单元格
signal edited_cell(cell: InputCell)
## 单元格数据发生改变
signal cell_value_changed(cell: InputCell, coords: Vector2i, previous: String, value: String)
## 滚动条滚动
signal scroll_changed(coords: Vector2i)
## 行高发生改变
signal row_height_changed(value: int)
## 列宽发生改变
signal column_width_changed(value: int)
## 弹窗编辑器表格大小发生改变
signal popup_edit_box_size_changed(box_size: Vector2)
## 双击单元格进行编辑
@export var double_click_edit : bool = true
## 表格中的数据。格式:[code]data[row][column] = data[/code]
var grid_data := {}:
set(v):
grid_data = v
update_cell_list()
scroll_to(Vector2i(0,0))
self.data_set_changed.emit()
## 默认单元格大小
var default_tile_size : Vector2i
## 数据集,管理获取数据
var data_set : TableDataEditor_TableDataSet = TableDataEditor_TableDataSet.new():
set(v):
data_set = v
# 当前线程其他代码调用完成后调用这个
(func():
update_cell_list()
scroll_to(Vector2i(0,0))
).call_deferred()
## 行对应的行高
var row_to_height_map := {}
## 列对应的列宽
var column_to_width_map := {}
var __init_node = InjectUtil.auto_inject(self, "_", true)
var _table_container : TableContainer
var _popup_edit_box : PopupEditBox
var _v_scroll_bar : VScrollBar
var _h_scroll_bar : HScrollBar
var _update_grid_data_timer : Timer
var _serial_number_container : SerialNumberContainer
# 是否允许发出取消选中 cell 的信号,用于编辑表格数据,编辑的时候代表这个单元格还是被选中的
var _enabled_emit_deselected_signal := true
# 原始坐标位置对应的单元格
var _origin_coords_to_cell_map := {}
# 单元格对应的原点坐标位置
var _cell_to_origin_coords_map := {}
# 当前选中的单元格
var _selected_cell : InputCell:
set(v):
_selected_cell = v
if v != null:
_last_cell = v
# 最后一次选中的单元格
var _last_cell : InputCell
# 上一次左上角的坐标位置
var _latest_top_left := Vector2i()
# 正在按着 alt 键
var _pressing_alt := false
# 是否已经开始更新
var _updated : bool = false
#============================================================
# SetGet
#============================================================
## 获取编辑弹窗
func get_edit_dialog() -> PopupEditBox:
return _popup_edit_box
func get_grid_data() -> Dictionary:
return grid_data
func get_data_set() -> TableDataEditor_TableDataSet:
return data_set
## 获取当前滚动到的左上角位置
func get_scroll_top_left() -> Vector2i:
return Vector2i( _h_scroll_bar.value, _v_scroll_bar.value )
## 获取滚动条最顶部 Y 的值
func get_scroll_top() -> int:
return int(_v_scroll_bar.value)
## 获取滚动条最左边 X 的值
func get_scroll_left() -> int:
return int(_h_scroll_bar.value)
## 获取这个 cell 的当前坐标位置
func get_cell_coords(cell: InputCell) -> Vector2i:
return get_scroll_top_left() + _cell_to_origin_coords_map.get(cell, Vector2i(-1, -1))
## 获取这个坐标上的 cell
func get_cell_node(coords: Vector2i) -> InputCell:
var origin_coords = coords - get_scroll_top_left()
return _origin_coords_to_cell_map.get(origin_coords) as InputCell
## 获取列宽
func get_column_width(column: int, default_width: int = 0) -> int:
if default_width <= 0:
default_width = _table_container.get_tile_size().x
return column_to_width_map.get(column, default_width)
## 获取行高
func get_row_height(row: int, default_heigt : int = 0):
if default_heigt <= 0:
default_heigt = _table_container.get_tile_size().y
return row_to_height_map.get(row, default_heigt)
## 获取列宽数据,数据中的 key 为列值,对应列宽
func get_column_width_data() -> Dictionary:
return column_to_width_map
## 获取行高数据,数据中的 key 为行值,对应行宽
func get_row_height_data() -> Dictionary:
return row_to_height_map
## 获取单元格行列大小数量
func get_column_row_size() -> Vector2i:
return _table_container.get_grid_row_column_count_size()
## 获取单元格当前整个矩形数据的位置
func get_current_rect() -> Rect2i:
return Rect2i(get_scroll_top_left(), get_column_row_size())
#============================================================
# 内置
#============================================================
func _ready() -> void:
# 滚动条
_h_scroll_bar.scrolling.connect(func():
_h_scroll_bar.max_value = _h_scroll_bar.value + 100
update_cell_list()
)
_v_scroll_bar.scrolling.connect(func():
_v_scroll_bar.max_value = _v_scroll_bar.value + 100
update_cell_list()
)
# 编辑表格窗。隐藏就更新对应的单元格的数据
_popup_edit_box.popup_hide.connect(func(value):
# 弹窗消失后才允许发送取消选中的信号
_enabled_emit_deselected_signal = true
# 更新选中的 cell
if _last_cell:
_last_cell.set_value( value )
var coords = get_cell_coords(_last_cell)
alter_value(coords, value)
self.edited_cell.emit(_last_cell)
)
# 必须要等空闲时间时调用,否则 _table_container 中的节点没有加载完成,则看不到节点的大小
update_serial_num.call_deferred(Vector2i(0, 0))
self.default_tile_size = _table_container.get_tile_size()
# 切换窗口时取消 alt
while get_window() == null:
await Engine.get_main_loop().process_frame
_pressing_alt = false
# 更新表格数据
update_cell_list()
_serial_number_container.update_serial_number(Vector2i(0,0))
func _notification(what):
if what == NOTIFICATION_WM_WINDOW_FOCUS_OUT:
_pressing_alt = false
func _unhandled_input(event):
if event is InputEventKey:
# 按下 alt 键
if event.keycode == KEY_ALT:
# if not _pressing_alt and event.is_pressed():
# print("[ TableEdit ] 按下了 Alt 键")
_pressing_alt = event.is_pressed()
#============================================================
# 自定义
#============================================================
# 真正进行修改行高,但数据不会缓存到数据中
func _alter_row_height(row: int, height: int):
height = max(height, default_tile_size.y)
for cell in _table_container.get_row_cells(row - get_scroll_top()):
cell.custom_minimum_size.y = height
_serial_number_container.update_row_height(row - get_scroll_top(), height)
# 真正进行修改单元格宽度,但数据不会缓存到数据中
func _alter_column_width(column: int, width: int):
width = max(width, default_tile_size.x)
for cell in _table_container.get_column_cells( column - get_scroll_left() ):
cell.custom_minimum_size.x = width
_serial_number_container.update_column_width(column - get_scroll_left(), width)
## 修改单元格数据
##[br]
##[br][code]coords[/code] 修改的坐标位置
##[br][code]value[/code] 修改的值,如果为需改为 [code]""[/code],则会删除掉这个数据
##[br][code]emit_signal_state[/code] 是否发送信号
func alter_value(
coords: Vector2i,
value: String,
emit_signal_state: bool = true
) -> void:
var previous = data_set.get_value(coords)
if value:
if previous != value:
data_set.set_value(coords, value)
if emit_signal_state:
var cell = get_cell_node(coords)
self.cell_value_changed.emit(cell, coords, previous, value )
else:
if data_set.remove_value(coords):
if emit_signal_state:
var cell = get_cell_node(coords)
self.cell_value_changed.emit(cell, coords, previous, "")
## 修改行高
##[br]
##[br][code]row[/code] 所在的行,从 1 开始
##[br][code]height[/code] 设置的行高
func alter_row_height(row: int, height: int):
row_to_height_map[row] = height
_alter_row_height(row, height)
## 修改列宽
##[br]
##[br][code]column[/code] 所在的列,从 1 开始
##[br][code]width[/code] 设置的列宽
func alter_column_width(column: int, width: int):
column_to_width_map[column] = width
_alter_column_width(column, width)
## 更新单元格信息(使用计时器缓冲更新,防止同一时间多次重复调用)
func update_cell_list():
if _updated:
return
_updated = true
await Engine.get_main_loop().process_frame
_updated = false
force_update_cell_list()
# 真正实际执行的更新
func force_update_cell_list():
# 更新数据
var top_left = get_scroll_top_left()
var coords : Vector2i
for cell in _cell_to_origin_coords_map:
coords = get_cell_coords(cell)
cell.show_value(data_set.get_value(coords))
# 更新单元格的宽高
var grid_row_column_size = _table_container.get_grid_row_column_count_size()
for column in range(top_left.x, top_left.x + grid_row_column_size.x):
_alter_column_width(column, get_column_width(column, 0) )
for row in range(top_left.y, top_left.y + grid_row_column_size.y):
_alter_row_height(row, get_row_height(row, 0) )
if _latest_top_left != top_left:
_latest_top_left = top_left
self.scroll_changed.emit( _latest_top_left )
_popup_edit_box.showed = false
_popup_edit_box.get_edit_box().visible = false
_serial_number_container.update_serial_number(top_left)
## 更新行列序号的值
##[br]
##[br][code]left_top[/code] 左上角行列值
func update_serial_num(left_top: Vector2i):
_serial_number_container.update_column_width(left_top.x, default_tile_size.x)
_serial_number_container.update_row_height(left_top.y, default_tile_size.y)
## 滚动到指定位置
func scroll_to(left_top: Vector2i):
_h_scroll_bar.value = left_top.x
_v_scroll_bar.value = left_top.y
_h_scroll_bar.scrolling.emit()
_v_scroll_bar.scrolling.emit()
## 编辑单元格
func edit_cell(cell_coords: Vector2i):
_enabled_emit_deselected_signal = false
# 设置选中的 cell
var cell = get_cell_node(cell_coords)
_selected_cell = cell
# 弹窗
_popup_edit_box.text = data_set.get_value(cell_coords)
_popup_edit_box.popup(Rect2(cell.global_position, Vector2(0,0)))
self.ready_edit_cell.emit(cell)
## 切换到下一个位置进行编辑。表格坐标是从 Vector2i(1, 1) 开始的,不是 Vector2i(0, 0)
func edit_to_next_cell(coords: Vector2i, direction : Vector2i):
if _selected_cell:
var next_coords = coords + direction
next_coords.x = max(1, next_coords.x)
next_coords.y = max(1, next_coords.y)
_popup_edit_box.showed = false
_popup_edit_box.showed = true
# 如果所在的单元格超出当前视图内的单元格,则进行滚动
if direction.x > 0 or direction.y > 0:
var get_width_height_callback : Callable = self.get_column_width \
if direction.x != 0 \
else self.get_row_height
var dir_idx = 0 \
if direction.x != 0 \
else 1
var total = 0
var top_left = get_scroll_top_left()
for i in range(next_coords[dir_idx] - 1, top_left[dir_idx] - 1, -1):
total += abs(get_width_height_callback.call(i)) + 8
if total > self.size[dir_idx]:
# 超出屏幕则换行
scroll_to(top_left + direction * (i - top_left[dir_idx] + 1))
break
else:
var rect = get_current_rect()
rect.size -= Vector2i.ONE
if not rect.has_point(next_coords):
var top_left = get_scroll_top_left()
scroll_to(top_left + direction)
# 切换到下一个网格的位置编辑网格
await Engine.get_main_loop().create_timer(0.1).timeout
edit_cell.call_deferred(next_coords)
#============================================================
# 连接信号
#============================================================
# 添加新的单元格时
func _newly_added_cell(coords: Vector2i, new_cell: InputCell):
# 滑轮滚动
new_cell.gui_input.connect(func(event):
# 单元格 Input
if event is InputEventMouseButton and event.is_pressed():
var scroll_bar : ScrollBar
if _pressing_alt:
scroll_bar = _h_scroll_bar
else:
scroll_bar = _v_scroll_bar
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
scroll_bar.value += scroll_bar.step
scroll_bar.scrolling.emit()
elif event.button_index == MOUSE_BUTTON_WHEEL_UP:
scroll_bar.value -= scroll_bar.step
scroll_bar.scrolling.emit()
)
# 单元格行列坐标映射
_cell_to_origin_coords_map[new_cell] = coords
_origin_coords_to_cell_map[coords] = new_cell
update_cell_list()
# 选中单元格
new_cell.focus_entered.connect(func():
_selected_cell = new_cell
if _popup_edit_box.showed:
_popup_edit_box.showed = false
if _selected_cell:
# 取消上次选中的单元格
self.deselected_cell.emit(_selected_cell)
_selected_cell = null
# 当前 cell
self.selected_cell.emit(new_cell)
_popup_edit_box.showed = false
)
# 取消选中单元格
new_cell.focus_exited.connect(func():
if _enabled_emit_deselected_signal:
_selected_cell = null
self.deselected_cell.emit(new_cell)
)
# 单击
new_cell.single_clicked.connect(func():
if not double_click_edit:
edit_cell(get_cell_coords(new_cell))
self.single_clicked_cell.emit(new_cell)
)
# 双击
new_cell.double_clicked.connect(func():
if double_click_edit:
edit_cell(get_cell_coords(new_cell))
self.double_clicked_cell.emit(new_cell)
)
# 水平拖拽移动
new_cell.h_dragged.connect(func(distance: float, pressed_node_size: Vector2i):
# 表格当前坐标位置
var current_coords = get_cell_coords(new_cell)
var width = pressed_node_size.x + int(distance)
alter_column_width(current_coords.x, width)
# 记录改变的列宽
column_to_width_map[current_coords.x] = width
self.column_width_changed.emit(width)
)
# 垂直拖拽移动
new_cell.v_dragged.connect(func(distance: float, pressed_node_size: Vector2i):
# 表格当前坐标位置
var current_coords = get_cell_coords(new_cell)
var height = pressed_node_size.y + int(distance)
alter_row_height(current_coords.y, height)
# 记录改变的行高
row_to_height_map[current_coords.y] = height
self.row_height_changed.emit(height)
)
func _on_table_container_grid_cell_size_changed(grid_size):
# 更新序号
_serial_number_container.update_grid_cell_count(grid_size)
func _on_popup_edit_box_box_size_changed(box_size):
self.popup_edit_box_size_changed.emit(box_size)
func _on_popup_edit_box_input_switch_char(character):
match character:
KEY_TAB:
var coords = get_cell_coords(_selected_cell)
edit_to_next_cell(coords, Vector2i.LEFT if Input.is_key_pressed(KEY_SHIFT) else Vector2i.RIGHT)
KEY_ENTER:
var coords = get_cell_coords(_selected_cell)
edit_to_next_cell(coords, Vector2i.UP if Input.is_key_pressed(KEY_SHIFT) else Vector2i.DOWN)

View File

@ -1,118 +0,0 @@
[gd_scene load_steps=6 format=3 uid="uid://ctppgkl2dpksd"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_edit.gd" id="1_b56jx"]
[ext_resource type="Script" path="res://addons/table_data_editor/src/table_data_editor/table_edit/serial_number_container.gd" id="2_irx05"]
[ext_resource type="PackedScene" path="res://addons/table_data_editor/src/table_data_editor/table_edit/cell/serial_number_cell/serial_number_cell.tscn" id="3_xe81h"]
[ext_resource type="PackedScene" uid="uid://d02f0rqt6qnpv" path="res://addons/table_data_editor/src/table_data_editor/table_edit/table_container/table_container.tscn" id="4_sm235"]
[ext_resource type="PackedScene" uid="uid://4xts0ha85fja" path="res://addons/table_data_editor/src/table_data_editor/table_edit/edit_box_window/edit_box_window.tscn" id="6_mt1mb"]
[node name="table_edit" type="MarginContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/margin_left = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_right = 2
theme_override_constants/margin_bottom = 2
script = ExtResource("1_b56jx")
[node name="serial_number_container" type="GridContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
theme_override_constants/h_separation = 0
theme_override_constants/v_separation = 0
columns = 2
script = ExtResource("2_irx05")
serial_number_cell = ExtResource("3_xe81h")
[node name="space" type="Control" parent="serial_number_container"]
unique_name_in_owner = true
custom_minimum_size = Vector2(27, 35)
layout_mode = 2
[node name="Control" type="Control" parent="serial_number_container"]
clip_contents = true
layout_mode = 2
mouse_filter = 2
[node name="h_serial_number_container" type="HBoxContainer" parent="serial_number_container/Control"]
unique_name_in_owner = true
layout_mode = 0
theme_override_constants/separation = 0
[node name="Control2" type="Control" parent="serial_number_container"]
clip_contents = true
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
mouse_filter = 2
[node name="v_serial_number_container" type="VBoxContainer" parent="serial_number_container/Control2"]
unique_name_in_owner = true
layout_mode = 0
theme_override_constants/separation = 0
[node name="Control3" type="Control" parent="serial_number_container"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
mouse_filter = 2
[node name="grid_container" type="GridContainer" parent="serial_number_container/Control3"]
unique_name_in_owner = true
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/h_separation = 0
theme_override_constants/v_separation = 0
columns = 2
[node name="table_container" parent="serial_number_container/Control3/grid_container" instance=ExtResource("4_sm235")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="v_scroll_bar" type="VScrollBar" parent="serial_number_container/Control3/grid_container"]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 1
min_value = 1.0
step = 1.0
page = 10.0
value = 1.0
exp_edit = true
[node name="h_scroll_bar" type="HScrollBar" parent="serial_number_container/Control3/grid_container"]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 1
min_value = 1.0
step = 1.0
page = 10.0
value = 1.0
exp_edit = true
[node name="Control" type="Control" parent="serial_number_container/Control3/grid_container"]
layout_mode = 2
[node name="update_grid_data_timer" type="Timer" parent="serial_number_container/Control3/grid_container"]
unique_name_in_owner = true
wait_time = 0.02
one_shot = true
autostart = true
[node name="popup_edit_box" parent="." instance=ExtResource("6_mt1mb")]
unique_name_in_owner = true
layout_mode = 2
[connection signal="grid_cell_size_changed" from="serial_number_container/Control3/grid_container/table_container" to="." method="_on_table_container_grid_cell_size_changed"]
[connection signal="newly_added_cell" from="serial_number_container/Control3/grid_container/table_container" to="." method="_newly_added_cell"]
[connection signal="box_size_changed" from="popup_edit_box" to="." method="_on_popup_edit_box_box_size_changed"]
[connection signal="input_switch_char" from="popup_edit_box" to="." method="_on_popup_edit_box_input_switch_char"]

View File

@ -1,45 +0,0 @@
#============================================================
# Inject Util
#============================================================
# - author: zhangxuetu
# - datetime: 2023-05-14 23:53:46
# - version: 4.0
#============================================================
## 注入节点工具
class_name InjectUtil
## 自动注入 unique (唯一名称)节点属性
##[br]
##[br][code]parent[/code] 目标节点,对这个节点的属性进行自动注入节点属性
##[br][code]prefix[/code] 注入的属性的前缀值
##[br]示例:
##[codeblock]
##extends Node
##
##var __init_node__ = InjectUtil.auto_inject(self, "_")
### 当前场景中有 %sprite 、%collision 节点则会自动获取并自动设置下面两个属性
##var _sprite : Sprite2D
##var _collision: Collision
##
##[/codeblock]
static func auto_inject(parent: Node, prefix: String = "", open_err: bool = false):
var method : Callable = func():
for data in (parent.get_script() as GDScript).get_script_property_list():
if data['type'] == TYPE_OBJECT and parent[data['name']] == null:
var prop = str(data['name']).trim_prefix(prefix)
if parent.has_node("%" + prop):
# 注入属性
var node = parent.get_node_or_null("%" + prop)
if node:
parent[data['name']] = node
else:
if open_err:
printerr("没有 ", prop, " 属性相关节点")
if parent.is_inside_tree():
method.call()
else:
parent.tree_entered.connect(method, Object.CONNECT_ONE_SHOT)
return true

View File

@ -1,151 +0,0 @@
#============================================================
# Table Data Util
#============================================================
# - author: zhangxuetu
# - datetime: 2023-05-17 19:14:50
# - version: 4.0
#============================================================
class_name TableDataUtil
class SingletonData:
static func get_value(key, default: Callable):
if not Engine.has_meta(key):
Engine.set_meta(key, default.call())
return Engine.get_meta(key)
static func get_child_value(key, child_key, child_default: Callable):
var data = get_value(key, func(): return {})
if data.has(child_key):
return data[child_key]
else:
data[child_key] = child_default.call()
return data[child_key]
class Files:
static func load_file(path: String):
if FileAccess.file_exists(path):
var bytes = FileAccess.get_file_as_bytes(path)
return bytes_to_var(bytes)
static func make_dir(dir: String) -> bool:
if not DirAccess.dir_exists_absolute(dir):
DirAccess.make_dir_recursive_absolute(dir)
return true
return false
static func save_data(path: String, data) -> bool:
make_dir(path.get_base_dir())
if not path.is_empty():
var bytes : PackedByteArray = var_to_bytes(data)
var writer : FileAccess = FileAccess.open(path, FileAccess.WRITE)
if writer.get_open_error() != OK:
printerr("打开文件失败!", writer.get_open_error())
return false
writer.store_buffer(bytes)
if writer.get_error() != OK:
printerr("写入文件失败:", writer.get_error())
return false
writer = null
return true
return false
static func save_as_string(path:String, data):
make_dir(path.get_base_dir())
var writer = FileAccess.open(path, FileAccess.WRITE)
writer.store_string(
JSON.stringify(data)
if not data is String
else data
)
static func read_as_string(path: String) -> String:
if FileAccess.file_exists(path):
var reader = FileAccess.open(path, FileAccess.READ)
return reader.get_as_text()
return ""
static func read_csv_file(path: String, delim: String = ",") -> Array[PackedStringArray]:
if FileAccess.file_exists(path):
var reader = FileAccess.open(path, FileAccess.READ)
var lines : Array[PackedStringArray]= []
var line = reader.get_csv_line(delim)
while line != PackedStringArray([""]):
lines.append(line)
line = reader.get_csv_line(delim)
return lines
return []
static func get_absolute_path(path: String) -> String:
var reader = FileAccess.open(path, FileAccess.READ)
if reader:
return reader.get_path_absolute()
return ""
class Classes:
static func get_propertys(script: Script) -> Array[String]:
return SingletonData.get_child_value("TableDataUtil_Classes_propertys", script, func():
var list : Array[String] = []
list.append_array(script \
.get_script_property_list() \
.map(func(data): return data['name'])
.filter(func(name: String): return name.find(".") == -1)
)
return list
)
static func set_property_by_dict(object: Object, dict: Dictionary):
for property in dict:
if property in object:
object[property] = dict[property]
static func get_dict_by_property(object: Object) -> Dictionary:
var dict : Dictionary = {}
var list = get_propertys(object.get_script())
for property in get_propertys(object.get_script()):
dict[property] = object[property]
return dict
class Editor:
## 获取编辑器接口
static func get_editor_interface() -> EditorInterface:
if not Engine.is_editor_hint():
return null
const KEY = "TableDataUtil_get_editor_interface"
if Engine.has_meta(KEY):
return Engine.get_meta(KEY)
else:
var plugin = ClassDB.instantiate("EditorPlugin")
Engine.set_meta(KEY, plugin.get_editor_interface())
return plugin.get_editor_interface()
## 是否开启了插件
static func is_enabled() -> bool:
if not Engine.is_editor_hint():
return false
if get_editor_interface() == null:
return false
return get_editor_interface().is_plugin_enabled("table_data_editor")
## 当前插件是否是打开的
static func is_main_node() -> bool:
var node = get_editor_interface().get_editor_main_screen()
for child in node.get_children():
if child is Control and child.visible:
# 显示的是当前插件的名称
return child.name == 'table_data_editor'
return false