Note:
This mini tutorial is for Godot 3.5. There are some changes that need to be made for Godot 4, check them out in this other gist
Since there is no built-in method to add context actions, we have to make our own.
To start, we need to get the file system dock. This can be done in the EditorPlugin. If we need it in any other script, we can just pass it to them.
tool
extends EditorPlugin
func _enter_tree() -> void:
var file_system := get_editor_interface().get_file_system_dock()
connect_file_system_context_actions(file_system)
There are two panels in the file system dock: a file Tree and an ItemList of files. Both of these have their own context menu popup. If you can't see the ItemList, you have to toggle the small icon button with two parallel bars at the top of the FileSystem dock.
The context menus are dynamic, so they are cleared every time they pop up. It won't be enough to get them once and add our menu items, we have to do it every time.
This method is used to get the two context menus and connect their about_to_show
methods.
func connect_file_system_context_actions(file_system_dock: FileSystemDock) -> void:
# There are two file systems: one is a Tree, the second is an ItemList.
# Both need to be handled. Toggle the button with two bars at the top to see both
var file_tree: Tree
var file_list: ItemList
for node in file_system_dock.get_children():
# Only the parent of the file tree and file list is a VSplit
if node is VSplitContainer:
file_tree = node.get_child(0)
file_list = node.get_child(1).get_child(1)
break
for node in file_system_dock.get_children():
var context_menu: PopupMenu = node as PopupMenu
if not context_menu: continue
# The order of the menus isn't always the same, but we can use their
# signal connections to find out if they belong to the list or the tree
var signals: Array = context_menu.get_signal_connection_list("id_pressed")
if not signals.empty():
match signals[0]["method"]:
"_tree_rmb_option":
context_menu.connect("about_to_show", self, "_on_file_tree_context_actions_about_to_show", [context_menu, file_tree])
"_file_list_rmb_option":
context_menu.connect("about_to_show", self, "_on_file_list_context_actions_about_to_show", [context_menu, file_list])
These two functions are what we connect them to. Since we want the same context menu for bot the list and the tree, but have to get the file path information in a different way for each, we use these to unify the way that the data is passed on.
We take the file path of the item that is selected and pass it on to the next method.
func _on_file_tree_context_actions_about_to_show(context_menu: PopupMenu, tree: Tree) -> void:
var selected := tree.get_next_selected(null)
if not selected: # Empty space was clicked
add_custom_context_actions(context_menu, "")
return
# The file system tree only has one column
var file_path := selected.get_metadata(0) as String
printt(selected, file_path)
add_custom_context_actions(context_menu, file_path)
# If you need multiselection you can modify add_custom_context_actions to take an array
var file_paths := []
while selected:
file_paths.append(selected.get_metadata(0))
selected = tree.get_next_selected(selected)
func _on_file_list_context_actions_about_to_show(context_menu: PopupMenu, list: ItemList) -> void:
if not list.get_selected_items().size() > 0: # Empty space was clicked
add_custom_context_actions(context_menu, "")
return
var item_index = list.get_selected_items()[0]
var selected := list.items[item_index] as Control
var file_path := list.get_item_metadata(item_index) as String
printt(selected, file_path)
add_custom_context_actions(context_menu, file_path)
This is the unified method that will build our context menu. We can get all the context information we need from the file path, as shown below.
Note that an item can be focused at the same time that the empty space was clicked - if you want to avoid this, just return after adding your menu items there.
# Called every time the file system context actions pop up
# Since they are dynamic, they are cleared every time and need to be refilled
func add_custom_context_actions(context_menu: PopupMenu, file_path: String) -> void:
var dir := Directory.new()
context_menu.add_separator()
if file_path == "":
context_menu.add_item("Clicked empty space")
# return # ignore the focused item if empty space was clicked
if dir.dir_exists(file_path):
print(file_path)
context_menu.add_item("A directory is selected")
if dir.file_exists(file_path):
context_menu.add_item("A file is selected")
if "res://addons" in file_path:
context_menu.add_item("Something in the addons directory is selected")
And that's it! If you have more questions, feel free to ask.
If you want to support me, you can