Skip to content

Commit e9abeb8

Browse files
committed
Add GridMap item categories
Adds GridMap item categories.
1 parent 0fdbf05 commit e9abeb8

File tree

6 files changed

+294
-32
lines changed

6 files changed

+294
-32
lines changed

doc/classes/MeshLibrary.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
Returns the first item with the given name, or [code]-1[/code] if no item is found.
3333
</description>
3434
</method>
35+
<method name="get_item_category" qualifiers="const">
36+
<return type="StringName" />
37+
<param index="0" name="id" type="int" />
38+
<description>
39+
Returns the item category for a specific item [param id]. A category string can include "/" as a delimiter to signal sub categories for a category tree view.
40+
</description>
41+
</method>
3542
<method name="get_item_list" qualifiers="const">
3643
<return type="PackedInt32Array" />
3744
<description>
@@ -115,6 +122,14 @@
115122
Removes the item.
116123
</description>
117124
</method>
125+
<method name="set_item_category">
126+
<return type="void" />
127+
<param index="0" name="id" type="int" />
128+
<param index="1" name="category" type="StringName" />
129+
<description>
130+
Sets the [param category] for a specific item [param id]. A category string can include "/" as a delimiter to create sub categories for the category tree view.
131+
</description>
132+
</method>
118133
<method name="set_item_mesh">
119134
<return type="void" />
120135
<param index="0" name="id" type="int" />

editor/scene/3d/mesh_library_editor_plugin.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ void MeshLibraryEditor::_import_scene_parse_node(Ref<MeshLibrary> p_library, Has
140140
item_id = p_library->get_last_unused_item_id();
141141
p_library->create_item(item_id);
142142
p_library->set_item_name(item_id, mesh_instance_node->get_name());
143+
144+
// Try to decipher some Node meta Variant data that can potentially be exported as item meta.
145+
{
146+
Variant _variant = mesh_instance_node->get_meta(SNAME("category"), StringName());
147+
StringName category = _variant;
148+
if (category) {
149+
p_library->set_item_category(item_id, category);
150+
}
151+
}
152+
143153
} else if (!p_merge) {
144154
WARN_PRINT(vformat("MeshLibrary export found a MeshInstance3D with a duplicated name '%s' in the exported scene that overrides a previously parsed MeshInstance3D item with the same name.", mesh_instance_node->get_name()));
145155
}

modules/gridmap/editor/grid_map_editor_plugin.cpp

Lines changed: 228 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,16 @@
4242
#include "editor/settings/editor_settings.h"
4343
#include "editor/themes/editor_scale.h"
4444
#include "scene/3d/camera_3d.h"
45+
#include "scene/gui/check_box.h"
4546
#include "scene/gui/dialogs.h"
47+
#include "scene/gui/item_list.h"
4648
#include "scene/gui/label.h"
4749
#include "scene/gui/menu_button.h"
4850
#include "scene/gui/separator.h"
51+
#include "scene/gui/slider.h"
52+
#include "scene/gui/spin_box.h"
53+
#include "scene/gui/split_container.h"
54+
#include "scene/gui/tree.h"
4955
#include "scene/main/window.h"
5056

5157
void GridMapEditor::_configure() {
@@ -934,48 +940,195 @@ void GridMapEditor::update_palette() {
934940
search_box->set_editable(true);
935941
info_message->hide();
936942

937-
Vector<int> ids;
938-
ids = mesh_library->get_item_list();
943+
TreeItem *ti_selected = item_tree->get_selected();
939944

940-
List<_CGMEItemSort> il;
941-
for (int i = 0; i < ids.size(); i++) {
945+
Vector<int> item_ids;
946+
if (ti_selected) {
947+
item_ids = ti_selected->get_metadata(0);
948+
} else {
949+
item_ids = mesh_library->get_item_list();
950+
}
951+
952+
const String searched_string = search_box->get_text().strip_edges().to_lower();
953+
954+
// Sort alphabetically by item name.
955+
List<_CGMEItemSort> items_sorted;
956+
for (int item_id : item_ids) {
942957
_CGMEItemSort is;
943-
is.id = ids[i];
944-
is.name = mesh_library->get_item_name(ids[i]);
945-
il.push_back(is);
958+
is.id = item_id;
959+
is.name = mesh_library->get_item_name(item_id);
960+
items_sorted.push_back(is);
946961
}
947-
il.sort();
962+
items_sorted.sort();
963+
964+
for (_CGMEItemSort &E : items_sorted) {
965+
int item_id = E.id;
948966

949-
String filter = search_box->get_text().strip_edges();
967+
String item_name = mesh_library->get_item_name(item_id);
968+
const StringName item_category = mesh_library->get_item_category(item_id);
969+
const Ref<Texture2D> &item_preview = mesh_library->get_item_preview(item_id);
950970

951-
int item = 0;
971+
bool skip_item = false;
952972

953-
for (_CGMEItemSort &E : il) {
954-
int id = E.id;
955-
String name = mesh_library->get_item_name(id);
956-
Ref<Texture2D> preview = mesh_library->get_item_preview(id);
973+
if (!searched_string.is_empty()) {
974+
bool item_matches_search_string = false;
957975

958-
if (name.is_empty()) {
959-
name = "#" + itos(id);
976+
if (item_name.contains(searched_string)) {
977+
item_matches_search_string = true;
978+
}
979+
if (!item_matches_search_string && String(item_category).contains(searched_string)) {
980+
item_matches_search_string = true;
981+
}
982+
983+
if (!item_matches_search_string) {
984+
skip_item = true;
985+
}
960986
}
961987

962-
if (!filter.is_empty() && !filter.is_subsequence_ofn(name)) {
988+
if (skip_item) {
963989
continue;
964990
}
965991

966-
mesh_library_palette->add_item("");
967-
if (preview.is_valid()) {
968-
mesh_library_palette->set_item_icon(item, preview);
969-
mesh_library_palette->set_item_tooltip(item, name);
992+
if (item_name.is_empty()) {
993+
item_name = "#" + itos(item_id);
994+
}
995+
996+
const int list_id = mesh_library_palette->add_item(item_name, item_preview);
997+
mesh_library_palette->set_item_text(list_id, item_name);
998+
mesh_library_palette->set_item_metadata(list_id, item_id);
999+
if (item_preview.is_valid()) {
1000+
mesh_library_palette->set_item_icon(list_id, item_preview);
1001+
mesh_library_palette->set_item_tooltip(list_id, item_name);
1002+
}
1003+
}
1004+
}
1005+
1006+
void GridMapEditor::_on_item_tree_item_activated() {
1007+
TreeItem *ti_selected = item_tree->get_selected();
1008+
if (ti_selected) {
1009+
bool collapsed = ti_selected->is_collapsed();
1010+
ti_selected->set_collapsed(!collapsed);
1011+
}
1012+
}
1013+
1014+
void GridMapEditor::_rebuild_item_tree() {
1015+
ERR_FAIL_NULL(item_tree);
1016+
1017+
item_tree->clear();
1018+
1019+
if (mesh_library.is_null()) {
1020+
return;
1021+
}
1022+
1023+
const Vector<int> &item_ids = mesh_library->get_item_list();
1024+
1025+
item_tree->create_item();
1026+
TreeItem *tree_root = item_tree->get_root();
1027+
tree_root->set_text(0, "All");
1028+
tree_root->set_text(1, vformat("(%s)", item_ids.size()));
1029+
tree_root->set_metadata(0, item_ids);
1030+
1031+
LocalVector<StringName> root_categories;
1032+
1033+
AHashMap<StringName, HashSet<int>> category_to_items;
1034+
AHashMap<StringName, HashSet<StringName>> category_to_category_children;
1035+
1036+
for (int item_id = 0; item_id < item_ids.size(); item_id++) {
1037+
const StringName item_category = mesh_library->get_item_category(item_id);
1038+
1039+
Vector<String> item_categories = String(item_category).split("/", false);
1040+
1041+
String item_category_path = "";
1042+
for (int i = 0; i < item_categories.size(); i++) {
1043+
const String category = item_categories[i];
1044+
1045+
if (i == 0) {
1046+
if (!root_categories.has(category)) {
1047+
root_categories.push_back(category);
1048+
}
1049+
}
1050+
1051+
String parent_item_category_path = item_category_path;
1052+
1053+
item_category_path = item_category_path.path_join(category);
1054+
1055+
{
1056+
AHashMap<StringName, HashSet<int>>::Iterator existing = category_to_items.find(item_category_path);
1057+
if (!existing) {
1058+
existing = category_to_items.insert(item_category_path, HashSet<int>());
1059+
}
1060+
existing->value.insert(item_id);
1061+
}
1062+
1063+
{
1064+
{
1065+
AHashMap<StringName, HashSet<StringName>>::Iterator existing = category_to_category_children.find(parent_item_category_path);
1066+
if (!existing) {
1067+
existing = category_to_category_children.insert(parent_item_category_path, HashSet<StringName>());
1068+
}
1069+
existing->value.insert(item_category_path);
1070+
}
1071+
{
1072+
AHashMap<StringName, HashSet<StringName>>::Iterator existing = category_to_category_children.find(item_category_path);
1073+
if (!existing) {
1074+
existing = category_to_category_children.insert(item_category_path, HashSet<StringName>());
1075+
}
1076+
}
1077+
}
9701078
}
971-
mesh_library_palette->set_item_text(item, name);
972-
mesh_library_palette->set_item_metadata(item, id);
1079+
}
1080+
1081+
ItemCategoryMapping item_mapping;
1082+
item_mapping.category_to_category_children = category_to_category_children;
1083+
item_mapping.category_to_items = category_to_items;
9731084

974-
if (selected_palette == id) {
975-
mesh_library_palette->select(item);
1085+
for (const StringName &root_category : root_categories) {
1086+
_add_child_categories_recursive(
1087+
item_tree,
1088+
tree_root,
1089+
root_category,
1090+
item_mapping);
1091+
}
1092+
1093+
update_palette();
1094+
}
1095+
1096+
void GridMapEditor::_add_child_categories_recursive(Tree *p_item_tree, TreeItem *p_ti_parent, const StringName &p_category, const GridMapEditor::ItemCategoryMapping &p_mapping) {
1097+
int category_user_count = 0;
1098+
Vector<int> category_items_meta = Vector<int>();
1099+
1100+
{
1101+
const AHashMap<StringName, HashSet<int>>::ConstIterator category_items_kv = p_mapping.category_to_items.find(p_category);
1102+
if (category_items_kv) {
1103+
const HashSet<int> &category_items = category_items_kv->value;
1104+
category_user_count = category_items.size();
1105+
1106+
for (int item_id : category_items) {
1107+
category_items_meta.push_back(item_id);
1108+
}
9761109
}
1110+
}
9771111

978-
item++;
1112+
TreeItem *ti_category = p_item_tree->create_item(p_ti_parent);
1113+
{
1114+
Vector<String> sub_categories = String(p_category).split("/", false);
1115+
ti_category->set_collapsed(true);
1116+
ti_category->set_text(0, vformat("%s", sub_categories[sub_categories.size() - 1]));
1117+
ti_category->set_metadata(0, category_items_meta);
1118+
ti_category->set_text(1, vformat("(%s)", category_user_count));
1119+
}
1120+
1121+
{
1122+
const AHashMap<StringName, HashSet<StringName>>::ConstIterator category_to_category_children_kv = p_mapping.category_to_category_children.find(p_category);
1123+
if (category_to_category_children_kv) {
1124+
const HashSet<StringName> &category_children = category_to_category_children_kv->value;
1125+
1126+
if (!category_children.is_empty()) {
1127+
for (const StringName &category_child : category_children) {
1128+
_add_child_categories_recursive(p_item_tree, ti_category, category_child, p_mapping);
1129+
}
1130+
}
1131+
}
9791132
}
9801133
}
9811134

@@ -1011,8 +1164,10 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
10111164
if (node) {
10121165
node->disconnect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
10131166
node->disconnect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_update_mesh_library));
1167+
node->disconnect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_rebuild_item_tree));
10141168
if (mesh_library.is_valid()) {
10151169
mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
1170+
mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::_rebuild_item_tree));
10161171
mesh_library = Ref<MeshLibrary>();
10171172
}
10181173
}
@@ -1049,7 +1204,10 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
10491204

10501205
node->connect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
10511206
node->connect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_update_mesh_library));
1207+
node->connect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_rebuild_item_tree));
10521208
_update_mesh_library();
1209+
1210+
_rebuild_item_tree();
10531211
}
10541212

10551213
void GridMapEditor::update_grid() {
@@ -1262,8 +1420,10 @@ void GridMapEditor::_update_cursor_instance() {
12621420
cursor_instance = RenderingServer::get_singleton()->instance_create2(cursor_mesh, scenario);
12631421
}
12641422

1265-
// Make the cursor translucent so that it can be distinguished from already-placed tiles.
1266-
RenderingServer::get_singleton()->instance_geometry_set_transparency(cursor_instance, 0.5);
1423+
if (cursor_instance.is_valid()) {
1424+
// Make the cursor translucent so that it can be distinguished from already-placed tiles.
1425+
RenderingServer::get_singleton()->instance_geometry_set_transparency(cursor_instance, 0.5);
1426+
}
12671427

12681428
_update_cursor_transform();
12691429
}
@@ -1541,7 +1701,7 @@ GridMapEditor::GridMapEditor() {
15411701

15421702
mesh_library_palette = memnew(ItemList);
15431703
mesh_library_palette->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1544-
add_child(mesh_library_palette);
1704+
mesh_library_palette->set_h_size_flags(SIZE_EXPAND_FILL);
15451705
mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL);
15461706
mesh_library_palette->connect(SceneStringName(gui_input), callable_mp(this, &GridMapEditor::_mesh_library_palette_input));
15471707

@@ -1555,6 +1715,45 @@ GridMapEditor::GridMapEditor() {
15551715
info_message->set_anchors_and_offsets_preset(PRESET_FULL_RECT, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE);
15561716
mesh_library_palette->add_child(info_message);
15571717

1718+
{
1719+
item_palette_container = memnew(VBoxContainer);
1720+
item_palette_container->set_h_size_flags(SIZE_EXPAND_FILL);
1721+
item_palette_container->set_v_size_flags(SIZE_EXPAND_FILL);
1722+
add_child(item_palette_container);
1723+
1724+
ScrollContainer *item_tree_scrollcontainer = memnew(ScrollContainer);
1725+
item_tree_scrollcontainer->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
1726+
1727+
VBoxContainer *scrollcontainer_vbox = memnew(VBoxContainer);
1728+
scrollcontainer_vbox->set_custom_minimum_size(Size2(256, 0) * EDSCALE);
1729+
scrollcontainer_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
1730+
scrollcontainer_vbox->set_v_size_flags(SIZE_EXPAND_FILL);
1731+
item_tree_scrollcontainer->add_child(scrollcontainer_vbox);
1732+
1733+
item_tree = memnew(Tree);
1734+
item_tree->set_select_mode(Tree::SelectMode::SELECT_SINGLE);
1735+
item_tree->connect(SNAME("item_activated"), callable_mp(this, &GridMapEditor::_on_item_tree_item_activated));
1736+
item_tree->connect(SceneStringName(item_selected), callable_mp(this, &GridMapEditor::update_palette));
1737+
item_tree->set_columns(2);
1738+
item_tree->set_column_expand(0, true);
1739+
item_tree->set_column_expand(1, false);
1740+
item_tree->set_column_custom_minimum_width(1, 64 * EDSCALE);
1741+
item_tree->set_h_size_flags(SIZE_EXPAND_FILL);
1742+
item_tree->set_v_size_flags(SIZE_EXPAND_FILL);
1743+
item_tree->set_hide_root(false);
1744+
1745+
scrollcontainer_vbox->add_child(item_tree);
1746+
1747+
HSplitContainer *hsplitcontainer = memnew(HSplitContainer);
1748+
hsplitcontainer->set_split_offset(256 * EDSCALE);
1749+
hsplitcontainer->set_h_size_flags(SIZE_EXPAND_FILL);
1750+
hsplitcontainer->set_v_size_flags(SIZE_EXPAND_FILL);
1751+
hsplitcontainer->add_child(item_tree_scrollcontainer);
1752+
hsplitcontainer->add_child(mesh_library_palette);
1753+
1754+
item_palette_container->add_child(hsplitcontainer);
1755+
}
1756+
15581757
edit_axis = Vector3::AXIS_Y;
15591758
edit_floor[0] = -1;
15601759
edit_floor[1] = -1;

0 commit comments

Comments
 (0)