Skip to content

Commit 4eaf1bf

Browse files
authored
Added panel to the editor to view and search UIDs (#1144)
Added panel to the editor to view and search UIDs
1 parent b770ba5 commit 4eaf1bf

4 files changed

Lines changed: 320 additions & 1 deletion

File tree

‎editor/docks/uid_viewer_dock.cpp‎

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/**************************************************************************/
2+
/* uid_viewer_dock.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* REDOT ENGINE */
6+
/* https://redotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2024-present Redot Engine contributors */
9+
/* (see REDOT_AUTHORS.md) */
10+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
11+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
12+
/* */
13+
/* Permission is hereby granted, free of charge, to any person obtaining */
14+
/* a copy of this software and associated documentation files (the */
15+
/* "Software"), to deal in the Software without restriction, including */
16+
/* without limitation the rights to use, copy, modify, merge, publish, */
17+
/* distribute, sublicense, and/or sell copies of the Software, and to */
18+
/* permit persons to whom the Software is furnished to do so, subject to */
19+
/* the following conditions: */
20+
/* */
21+
/* The above copyright notice and this permission notice shall be */
22+
/* included in all copies or substantial portions of the Software. */
23+
/* */
24+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
25+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
26+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
27+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
28+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
29+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
30+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
31+
/**************************************************************************/
32+
33+
#include "editor/docks/uid_viewer_dock.h"
34+
#include "core/io/resource_loader.h"
35+
#include "core/io/resource_uid.h"
36+
#include "editor/docks/filesystem_dock.h"
37+
#include "editor/file_system/editor_file_system.h"
38+
#include "scene/main/window.h" // For MouseButton enum in Godot 4.3+
39+
#include "servers/display_server.h"
40+
41+
UIDViewerDock::UIDViewerDock() {
42+
set_name("UID Viewer");
43+
44+
set_mouse_filter(Control::MOUSE_FILTER_STOP);
45+
set_focus_mode(Control::FOCUS_ALL);
46+
47+
// Top bar
48+
HBoxContainer *top_bar = memnew(HBoxContainer);
49+
add_child(top_bar);
50+
51+
search_edit = memnew(LineEdit);
52+
search_edit->set_placeholder("Search for UID or path...");
53+
search_edit->set_h_size_flags(SIZE_EXPAND_FILL);
54+
search_edit->connect("text_changed", callable_mp(this, &UIDViewerDock::_on_search_text_changed));
55+
top_bar->add_child(search_edit);
56+
57+
refresh_button = memnew(Button);
58+
refresh_button->set_text("Refresh");
59+
refresh_button->connect("pressed", callable_mp(this, &UIDViewerDock::_on_refresh_pressed));
60+
top_bar->add_child(refresh_button);
61+
62+
// Tree
63+
uid_tree = memnew(Tree);
64+
uid_tree->set_hide_root(true);
65+
uid_tree->set_columns(2);
66+
uid_tree->set_column_title(0, "UID");
67+
uid_tree->set_column_title(1, "Resource Path");
68+
uid_tree->set_column_custom_minimum_width(0, 280);
69+
uid_tree->set_column_expand(1, true);
70+
uid_tree->set_v_size_flags(SIZE_EXPAND_FILL);
71+
uid_tree->set_h_size_flags(SIZE_EXPAND_FILL);
72+
uid_tree->set_allow_rmb_select(true);
73+
uid_tree->connect("item_activated", callable_mp(this, &UIDViewerDock::_on_item_activated));
74+
add_child(uid_tree);
75+
76+
// Context menu (owned by dock)
77+
context_menu = memnew(PopupMenu);
78+
context_menu->add_item("Copy UID");
79+
context_menu->add_item("Copy Path");
80+
context_menu->connect("id_pressed", callable_mp(this, &UIDViewerDock::_on_context_menu_id_pressed));
81+
add_child(context_menu);
82+
83+
_refresh_uid_list();
84+
85+
uid_tree->connect("item_mouse_selected", callable_mp(this, &UIDViewerDock::_on_tree_rmb_selected));
86+
87+
// Auto-refresh on filesystem changes
88+
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &UIDViewerDock::_refresh_uid_list));
89+
}
90+
91+
void UIDViewerDock::_refresh_uid_list() {
92+
uid_tree->clear();
93+
TreeItem *root = uid_tree->create_item();
94+
95+
EditorFileSystem *efs = EditorFileSystem::get_singleton();
96+
if (!efs || !efs->get_filesystem()) {
97+
return;
98+
}
99+
100+
Vector<String> queue;
101+
queue.push_back("res://");
102+
103+
while (!queue.is_empty()) {
104+
String dir_path = queue[queue.size() - 1];
105+
queue.resize(queue.size() - 1);
106+
107+
EditorFileSystemDirectory *dir = efs->get_filesystem_path(dir_path);
108+
if (!dir) {
109+
continue;
110+
}
111+
112+
for (int i = 0; i < dir->get_file_count(); i++) {
113+
String file_path = dir_path + dir->get_file(i);
114+
115+
int64_t uid = ResourceLoader::get_resource_uid(file_path);
116+
if (uid != -1) {
117+
String uid_text = ResourceUID::get_singleton()->id_to_text(uid);
118+
TreeItem *item = uid_tree->create_item(root);
119+
item->set_text(0, uid_text);
120+
item->set_text(1, file_path);
121+
}
122+
}
123+
124+
for (int i = 0; i < dir->get_subdir_count(); i++) {
125+
EditorFileSystemDirectory *subdir = dir->get_subdir(i);
126+
queue.push_back(dir_path + subdir->get_name() + "/");
127+
}
128+
}
129+
130+
// Re-apply current search
131+
_on_search_text_changed(search_edit->get_text());
132+
}
133+
134+
void UIDViewerDock::_on_search_text_changed(const String &text) {
135+
String search_lower = text.to_lower().strip_edges();
136+
137+
TreeItem *root = uid_tree->get_root();
138+
if (!root) {
139+
return;
140+
}
141+
142+
if (search_lower.is_empty()) {
143+
_show_all_items(root);
144+
return;
145+
}
146+
147+
root->set_visible(true);
148+
_filter_tree_recursive(root, search_lower);
149+
}
150+
151+
void UIDViewerDock::_on_refresh_pressed() {
152+
EditorFileSystem::get_singleton()->scan();
153+
_refresh_uid_list();
154+
}
155+
156+
void UIDViewerDock::_on_item_activated() {
157+
TreeItem *selected = uid_tree->get_selected();
158+
if (!selected) {
159+
return;
160+
}
161+
162+
String file_path = selected->get_text(1);
163+
164+
FileSystemDock *fs_dock = FileSystemDock::get_singleton();
165+
if (fs_dock) {
166+
fs_dock->navigate_to_path(file_path);
167+
fs_dock->select_file(file_path);
168+
}
169+
}
170+
171+
void UIDViewerDock::_on_context_menu_id_pressed(int id) {
172+
if (!last_selected_item) {
173+
return;
174+
}
175+
176+
String text_to_copy;
177+
if (id == 0) { // Copy UID
178+
text_to_copy = last_selected_item->get_text(0);
179+
} else if (id == 1) { // Copy Path
180+
text_to_copy = last_selected_item->get_text(1);
181+
}
182+
183+
if (!text_to_copy.is_empty()) {
184+
DisplayServer::get_singleton()->clipboard_set(text_to_copy);
185+
}
186+
}
187+
188+
void UIDViewerDock::_show_all_items(TreeItem *item) {
189+
if (!item) {
190+
item = uid_tree->get_root();
191+
if (!item) {
192+
return;
193+
}
194+
}
195+
196+
item->set_visible(true);
197+
198+
TreeItem *child = item->get_first_child();
199+
while (child) {
200+
_show_all_items(child);
201+
child = child->get_next();
202+
}
203+
}
204+
205+
bool UIDViewerDock::_filter_tree_recursive(TreeItem *item, const String &search_lower) {
206+
if (!item) {
207+
return false;
208+
}
209+
210+
bool has_visible_child = false;
211+
212+
TreeItem *child = item->get_first_child();
213+
while (child) {
214+
bool child_visible = _filter_tree_recursive(child, search_lower);
215+
if (child_visible) {
216+
has_visible_child = true;
217+
}
218+
child = child->get_next();
219+
}
220+
221+
String uid_text = item->get_text(0).to_lower();
222+
String path_text = item->get_text(1).to_lower();
223+
224+
bool matches = uid_text.contains(search_lower) || path_text.contains(search_lower);
225+
bool result = matches || has_visible_child;
226+
227+
item->set_visible(result);
228+
229+
return result;
230+
}
231+
232+
void UIDViewerDock::_on_tree_rmb_selected(const Vector2 &p_pos, MouseButton p_button) {
233+
if (p_button != MouseButton::RIGHT) {
234+
return;
235+
}
236+
237+
TreeItem *item = uid_tree->get_item_at_position(p_pos);
238+
if (item) {
239+
uid_tree->set_selected(item, 0);
240+
last_selected_item = item;
241+
242+
if (context_menu) {
243+
context_menu->set_position(uid_tree->get_screen_position() + p_pos);
244+
context_menu->popup();
245+
}
246+
}
247+
}

‎editor/docks/uid_viewer_dock.h‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**************************************************************************/
2+
/* uid_viewer_dock.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* REDOT ENGINE */
6+
/* https://redotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2024-present Redot Engine contributors */
9+
/* (see REDOT_AUTHORS.md) */
10+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
11+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
12+
/* */
13+
/* Permission is hereby granted, free of charge, to any person obtaining */
14+
/* a copy of this software and associated documentation files (the */
15+
/* "Software"), to deal in the Software without restriction, including */
16+
/* without limitation the rights to use, copy, modify, merge, publish, */
17+
/* distribute, sublicense, and/or sell copies of the Software, and to */
18+
/* permit persons to whom the Software is furnished to do so, subject to */
19+
/* the following conditions: */
20+
/* */
21+
/* The above copyright notice and this permission notice shall be */
22+
/* included in all copies or substantial portions of the Software. */
23+
/* */
24+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
25+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
26+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
27+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
28+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
29+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
30+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
31+
/**************************************************************************/
32+
33+
#pragma once
34+
35+
#include "scene/gui/box_container.h"
36+
#include "scene/gui/button.h"
37+
#include "scene/gui/line_edit.h"
38+
#include "scene/gui/popup_menu.h"
39+
#include "scene/gui/tree.h"
40+
41+
class UIDViewerDock : public VBoxContainer {
42+
GDCLASS(UIDViewerDock, VBoxContainer);
43+
44+
private:
45+
Tree *uid_tree = nullptr;
46+
LineEdit *search_edit = nullptr;
47+
Button *refresh_button = nullptr;
48+
PopupMenu *context_menu = nullptr;
49+
TreeItem *last_selected_item = nullptr;
50+
51+
void _refresh_uid_list();
52+
void _on_search_text_changed(const String &text);
53+
void _on_refresh_pressed();
54+
void _on_item_activated();
55+
void _on_context_menu_id_pressed(int id);
56+
void _on_tree_rmb_selected(const Vector2 &p_pos, MouseButton p_button);
57+
void _show_all_items(TreeItem *item);
58+
bool _filter_tree_recursive(TreeItem *item, const String &search_lower);
59+
60+
public:
61+
UIDViewerDock();
62+
};

‎editor/editor_node.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
#include "servers/navigation_server_3d.h"
8080
#include "servers/rendering_server.h"
8181

82+
#include "docks/uid_viewer_dock.h"
8283
#include "editor/animation/animation_player_editor_plugin.h"
8384
#include "editor/asset_library/asset_library_editor_plugin.h"
8485
#include "editor/audio/audio_stream_preview.h"
@@ -8449,6 +8450,13 @@ EditorNode::EditorNode() {
84498450
center_split->add_child(bottom_panel);
84508451
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
84518452

8453+
//UIDViewer
8454+
8455+
UIDViewerDock *uid_viewer_dock = memnew(UIDViewerDock);
8456+
uid_viewer_dock->set_name("UID Viewer");
8457+
bottom_panel->add_item(TTR("UID Viewer"), uid_viewer_dock,
8458+
EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ResourceUID"), SNAME("EditorIcons")));
8459+
84528460
log = memnew(EditorLog);
84538461
Button *output_button = bottom_panel->add_item(TTRC("Output"), log, ED_SHORTCUT_AND_COMMAND("bottom_panels/toggle_output_bottom_panel", TTRC("Toggle Output Bottom Panel"), KeyModifierMask::ALT | Key::O));
84548462
log->set_tool_button(output_button);

‎platform/android/java/nativeSrcsConfigs/CMakeLists.txt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ set(OPENXR_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/openxr/include" CACHE STRIN
1414
file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**)
1515
file(GLOB_RECURSE HEADERS ${GODOT_ROOT_DIR}/*.h**)
1616

17-
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})
17+
add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}
18+
../../../../editor/docks/uid_viewer_dock.h
19+
../../../../editor/docks/uid_viewer_dock.cpp)
1820
target_include_directories(${PROJECT_NAME}
1921
SYSTEM PUBLIC
2022
${GODOT_ROOT_DIR}

0 commit comments

Comments
 (0)