HighNoonRoulette/addons/script-ide/override/override_panel.gd
2025-08-28 01:55:38 +02:00

336 lines
9.5 KiB
GDScript

@tool
extends PopupPanel
const FUNC_META: StringName = &"func"
@onready var filter_txt: LineEdit = %FilterTxt
@onready var class_func_tree: Tree = %ClassFuncTree
@onready var ok_btn: Button = %OkBtn
@onready var cancel_btn: Button = %CancelBtn
var plugin: EditorPlugin
var selections: Dictionary[String, bool] = {} # Used as Set.
var class_to_functions: Dictionary[StringName, PackedStringArray]
func _ready() -> void:
filter_txt.text_changed.connect(update_tree_filter.unbind(1))
class_func_tree.multi_selected.connect(func(item: TreeItem, col: int, selected: bool): save_selection(selected, item))
class_func_tree.item_activated.connect(generate_functions)
cancel_btn.pressed.connect(hide)
ok_btn.pressed.connect(generate_functions)
about_to_popup.connect(on_show)
if (plugin != null):
filter_txt.gui_input.connect(navigate_on_tree)
func navigate_on_tree(event: InputEvent):
if (event.is_action_pressed(&"ui_down", true)):
var selected: TreeItem = get_selected_tree_item()
if (selected == null):
return
var item: TreeItem = selected.get_next_in_tree()
if (item == null):
return
focus_tree_item(item)
elif (event.is_action_pressed(&"ui_up", true)):
var selected: TreeItem = get_selected_tree_item()
if (selected == null):
return
var item: TreeItem = selected.get_prev_in_tree()
if (item == null):
return
focus_tree_item(item)
elif (event.is_action_pressed(&"ui_page_down", true)):
var selected: TreeItem = get_selected_tree_item()
if (selected == null):
return
var item: TreeItem = selected.get_next_in_tree()
if (item == null):
return
for index: int in 4:
var next: TreeItem = item.get_next_in_tree()
if (next == null):
break
item = next
focus_tree_item(item)
elif (event.is_action_pressed(&"ui_page_up", true)):
var selected: TreeItem = get_selected_tree_item()
if (selected == null):
return
var item: TreeItem = selected.get_prev_in_tree()
if (item == null):
return
for index: int in 4:
var prev: TreeItem = item.get_prev_in_tree()
if (prev == null):
break
item = prev
focus_tree_item(item)
elif (event.is_action_pressed(&"ui_select", true)):
var selected: TreeItem = get_selected_tree_item()
if (selected == null):
return
if (!selected.is_selectable(0)):
selected.collapsed = !selected.collapsed
class_func_tree.accept_event()
return
if (selected.is_selected(0)):
selected.deselect(0)
save_selection(false, selected)
else:
selected.select(0)
save_selection(true, selected)
class_func_tree.accept_event()
elif (event.is_action_pressed(&"ui_text_submit", true)):
if (selections.size() == 0):
return
generate_functions()
class_func_tree.accept_event()
func get_selected_tree_item() -> TreeItem:
var selected: TreeItem = class_func_tree.get_selected()
if (selected == null):
selected = class_func_tree.get_root()
return selected
func focus_tree_item(item: TreeItem):
var was_selected: bool = item.is_selected(0)
item.select(0)
item.deselect(0)
if (was_selected):
item.select(0)
class_func_tree.ensure_cursor_is_visible()
class_func_tree.accept_event()
func update_tree_filter():
update_tree()
func save_selection(selected: bool, item: TreeItem):
if (selected):
selections[item.get_text(0)] = true
else:
selections.erase(item.get_text(0))
ok_btn.disabled = selections.size() == 0
func on_show():
class_func_tree.clear()
selections.clear()
ok_btn.disabled = true
filter_txt.text = &""
var script: Script = EditorInterface.get_script_editor().get_current_script()
class_to_functions = collect_all_class_functions(script) # [StringName, PackedStringArray]
if (class_to_functions.is_empty()):
return
update_tree()
filter_txt.grab_focus()
func update_tree():
class_func_tree.clear()
var text: String = filter_txt.text
var root: TreeItem = class_func_tree.create_item()
for class_name_str: StringName in class_to_functions.keys():
var class_item: TreeItem = root.create_child(0)
class_item.set_selectable(0, false)
class_item.set_text(0, class_name_str)
for function: String in class_to_functions.get(class_name_str):
var is_preselected: bool = selections.has(function)
if (is_preselected || text.is_empty() || text.is_subsequence_ofn(function)):
var func_item: TreeItem = class_item.create_child()
func_item.set_text(0, function)
if (plugin.keywords.has(function.get_slice("(", 0))):
func_item.set_icon(0, plugin.engine_func_icon)
else:
func_item.set_icon(0, plugin.func_icon)
if (is_preselected):
func_item.select(0)
func collect_all_class_functions(script: Script) -> Dictionary[StringName, PackedStringArray]:
var existing_funcs: Dictionary[String, bool] = {} # Used as Set.
for func_str: String in plugin.outline_cache.engine_funcs:
existing_funcs[func_str] = true
for func_str: String in plugin.outline_cache.funcs:
existing_funcs[func_str] = true
var class_to_functions: Dictionary[StringName, PackedStringArray] = collect_super_class_functions(script.get_base_script(), existing_funcs)
var native_class_to_functions: Dictionary[StringName, PackedStringArray] = collect_native_class_functions(script.get_instance_base_type(), existing_funcs)
return native_class_to_functions.merged(class_to_functions)
func collect_super_class_functions(base_script: Script, existing_funcs: Dictionary[String, bool]) -> Dictionary[StringName, PackedStringArray]:
var super_classes: Array[Script] = []
while (base_script != null):
super_classes.insert(0, base_script)
base_script = base_script.get_base_script()
var class_to_functions: Dictionary[StringName, PackedStringArray] = {}
for super_class: Script in super_classes:
var functions: PackedStringArray = collect_script_functions(super_class, existing_funcs)
if (functions.is_empty()):
continue
class_to_functions[super_class.get_global_name()] = functions
return class_to_functions
func collect_native_class_functions(native_class: StringName, existing_funcs: Dictionary[String, bool]) -> Dictionary[StringName, PackedStringArray]:
var super_native_classes: Array[StringName] = []
while (native_class != &""):
super_native_classes.insert(0, native_class)
native_class = ClassDB.get_parent_class(native_class)
var class_to_functions: Dictionary[StringName, PackedStringArray] = {}
for super_native_class: StringName in super_native_classes:
var functions: PackedStringArray = collect_class_functions(super_native_class, existing_funcs)
if (functions.is_empty()):
continue
class_to_functions[super_native_class] = functions
return class_to_functions
func collect_class_functions(native_class: StringName, existing_funcs: Dictionary[String, bool]):
var functions: PackedStringArray = []
for method: Dictionary in ClassDB.class_get_method_list(native_class, true):
if (method[&"flags"] & METHOD_FLAG_VIRTUAL <= 0):
continue
var func_name: String = method[&"name"]
if (existing_funcs.has(func_name)):
continue
func_name = create_function_signature(method)
functions.append(func_name)
return functions
func collect_script_functions(super_class: Script, existing_funcs: Dictionary[String, bool]) -> PackedStringArray:
var functions: PackedStringArray = []
for method: Dictionary in super_class.get_script_method_list():
var func_name: String = method[&"name"]
if (existing_funcs.has(func_name)):
continue
existing_funcs[func_name] = true
func_name = create_function_signature(method)
functions.append(func_name)
return functions
func create_function_signature(method: Dictionary) -> String:
var func_name: String = method[&"name"]
func_name += "("
var args: Array = method[&"args"]
var default_args: Array = method[&"default_args"]
var arg_index: int = 0
var default_arg_index: int = 0
var arg_str: String = ""
for arg: Dictionary in args:
if (arg_str != ""):
arg_str += ", "
arg_str += arg[&"name"]
var type: String = get_type(arg)
if (type != ""):
arg_str += ": " + type
if (args.size() - arg_index <= default_args.size()):
var default_arg: Variant = default_args[default_arg_index]
if (!default_arg):
var type_hint: int = arg[&"type"]
if (is_dictionary(type_hint)):
default_arg = {}
elif (is_array(type_hint)):
default_arg = []
arg_str += " = " + var_to_str(default_arg)
default_arg_index += 1
arg_index += 1
func_name += arg_str + ")"
var return_str: String = get_type(method[&"return"])
if (return_str == ""):
return_str = "void"
func_name += " -> " + return_str
return func_name
func generate_functions():
if (selections.size() == 0):
return
var generated_text: String = ""
for function: String in selections.keys():
generated_text += "\nfunc " + function + ":\n\tpass\n"
var editor: CodeEdit = EditorInterface.get_script_editor().get_current_editor().get_base_editor()
editor.text += generated_text
plugin.goto_line(editor.get_line_count() - 1)
hide()
func get_type(dict: Dictionary) -> String:
var type: String = dict[&"class_name"]
if (type != &""):
return type
var type_hint: int = dict[&"type"]
if (type_hint == 0):
return &""
type = type_string(type_hint)
if (is_dictionary(type_hint)):
var generic: String = dict[&"hint_string"]
if (generic != &""):
var generic_parts: PackedStringArray = generic.split(";")
if (generic_parts.size() == 2):
return type + "[" + generic_parts[0] + ", " + generic_parts[1] + "]"
if (is_array(type_hint)):
var generic: String = dict[&"hint_string"]
if (generic != &""):
return type + "[" + generic + "]"
return type
func is_dictionary(type_hint: int):
return type_hint == 27
func is_array(type_hint: int):
return type_hint == 28