Add categories to MeshLibrary
Co-authored-by: smix8 <52464204+smix8@users.noreply.github.com>
This commit is contained in:
@@ -32,6 +32,13 @@
|
||||
Returns the first item with the given name, or [code]-1[/code] if no item is found.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_item_category" qualifiers="const">
|
||||
<return type="StringName" />
|
||||
<param index="0" name="id" type="int" />
|
||||
<description>
|
||||
Returns the category for a specific item [param id]. See also [method set_item_category].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_item_count" qualifiers="const">
|
||||
<return type="int" />
|
||||
<description>
|
||||
@@ -121,6 +128,14 @@
|
||||
Removes the item.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_item_category">
|
||||
<return type="void" />
|
||||
<param index="0" name="id" type="int" />
|
||||
<param index="1" name="category" type="StringName" />
|
||||
<description>
|
||||
Sets the [param category] for a specific item [param id] for organization in the [GridMap] editor. A category can include [code]/[/code] as a delimiter to create sub categories.
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_item_mesh">
|
||||
<return type="void" />
|
||||
<param index="0" name="id" type="int" />
|
||||
|
||||
@@ -64,6 +64,8 @@ bool MeshLibraryEditor::MeshLibraryItem::_set(const StringName &p_name, const Va
|
||||
|
||||
if (p_name == "name") {
|
||||
mesh_library->set_item_name(mesh_id, p_value);
|
||||
} else if (p_name == "category") {
|
||||
mesh_library->set_item_category(mesh_id, p_value);
|
||||
} else if (p_name == "mesh") {
|
||||
mesh_library->set_item_mesh(mesh_id, p_value);
|
||||
} else if (p_name == "mesh_transform") {
|
||||
@@ -112,6 +114,8 @@ bool MeshLibraryEditor::MeshLibraryItem::_get(const StringName &p_name, Variant
|
||||
|
||||
if (p_name == "name") {
|
||||
r_ret = mesh_library->get_item_name(mesh_id);
|
||||
} else if (p_name == "category") {
|
||||
r_ret = mesh_library->get_item_category(mesh_id);
|
||||
} else if (p_name == "mesh") {
|
||||
r_ret = mesh_library->get_item_mesh(mesh_id);
|
||||
} else if (p_name == "mesh_transform") {
|
||||
@@ -143,6 +147,7 @@ void MeshLibraryEditor::MeshLibraryItem::_get_property_list(List<PropertyInfo> *
|
||||
}
|
||||
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, PNAME("name")));
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, PNAME("category")));
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, Mesh::get_class_static()));
|
||||
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m"));
|
||||
p_list->push_back(PropertyInfo(Variant::INT, PNAME("mesh_cast_shadow"), PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only"));
|
||||
@@ -739,6 +744,7 @@ MeshLibraryEditor::MeshLibraryEditor() {
|
||||
item_split->add_child(inspector);
|
||||
|
||||
inspector->add_custom_property_description("MeshLibraryItem", "name", TTRC("The item's name, shown in the editor. It can also be used to look up the item later using [method MeshLibrary.find_item_by_name]."));
|
||||
inspector->add_custom_property_description("MeshLibraryItem", "category", TTRC("The item's category, used to organize meshes in the [GridMap] editor. A category can include [code]/[/code] as a delimiter to create sub categories."));
|
||||
inspector->add_custom_property_description("MeshLibraryItem", "mesh", TTRC("The item's mesh. Used by other parts of the engine (e.g. [GridMap], which displays them in a 3D tile)."));
|
||||
inspector->add_custom_property_description("MeshLibraryItem", "mesh_transform", TTRC("The transform to apply to the item's mesh."));
|
||||
inspector->add_custom_property_description("MeshLibraryItem", "mesh_cast_shadow", TTRC("The shadow casting mode used by the item's mesh. See [enum RenderingServer.ShadowCastingSetting]"));
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/separator.h"
|
||||
#include "scene/gui/spin_box.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "scene/main/node.h"
|
||||
#include "scene/main/scene_tree.h"
|
||||
#include "scene/main/window.h"
|
||||
#include "servers/rendering/rendering_server.h"
|
||||
@@ -1106,55 +1109,195 @@ void GridMapEditor::update_palette() {
|
||||
search_box->set_editable(true);
|
||||
info_message->hide();
|
||||
|
||||
Vector<int> ids;
|
||||
ids = mesh_library->get_item_list();
|
||||
TreeItem *ti_selected = categories->get_selected();
|
||||
|
||||
List<_CGMEItemSort> il;
|
||||
for (int i = 0; i < ids.size(); i++) {
|
||||
_CGMEItemSort is;
|
||||
is.id = ids[i];
|
||||
is.name = mesh_library->get_item_name(ids[i]);
|
||||
il.push_back(is);
|
||||
Vector<int> item_ids;
|
||||
if (ti_selected) {
|
||||
item_ids = ti_selected->get_metadata(0);
|
||||
} else {
|
||||
item_ids = mesh_library->get_item_list();
|
||||
}
|
||||
il.sort();
|
||||
|
||||
String filter = search_box->get_text().strip_edges();
|
||||
const String searched_string = search_box->get_text().strip_edges().to_lower();
|
||||
|
||||
int item = 0;
|
||||
// Sort alphabetically by item name.
|
||||
List<_CGMEItemSort> items_sorted;
|
||||
for (int item_id : item_ids) {
|
||||
_CGMEItemSort is;
|
||||
is.id = item_id;
|
||||
is.name = mesh_library->get_item_name(item_id);
|
||||
items_sorted.push_back(is);
|
||||
}
|
||||
items_sorted.sort();
|
||||
|
||||
for (_CGMEItemSort &E : il) {
|
||||
int id = E.id;
|
||||
String name = mesh_library->get_item_name(id);
|
||||
Ref<Texture2D> preview = mesh_library->get_item_preview(id);
|
||||
for (_CGMEItemSort &E : items_sorted) {
|
||||
int item_id = E.id;
|
||||
|
||||
if (name.is_empty()) {
|
||||
name = "#" + itos(id);
|
||||
}
|
||||
String item_name = mesh_library->get_item_name(item_id);
|
||||
const StringName item_category = mesh_library->get_item_category(item_id);
|
||||
|
||||
if (!filter.is_empty() && !filter.is_subsequence_ofn(name)) {
|
||||
continue;
|
||||
}
|
||||
if (!searched_string.is_empty()) {
|
||||
bool item_matches_search_string = false;
|
||||
|
||||
mesh_library_palette->add_item("");
|
||||
if (preview.is_valid()) {
|
||||
mesh_library_palette->set_item_icon(item, preview);
|
||||
mesh_library_palette->set_item_tooltip(item, name);
|
||||
} else {
|
||||
Ref<Mesh> mesh = mesh_library->get_item_mesh(id);
|
||||
if (mesh.is_valid()) {
|
||||
// Fallback to the item's mesh preview.
|
||||
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(mesh, callable_mp(this, &GridMapEditor::_update_resource_preview).bind(item));
|
||||
if (item_name.to_lower().contains(searched_string)) {
|
||||
item_matches_search_string = true;
|
||||
}
|
||||
if (!item_matches_search_string && String(item_category).contains(searched_string)) {
|
||||
item_matches_search_string = true;
|
||||
}
|
||||
|
||||
if (!item_matches_search_string) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
mesh_library_palette->set_item_text(item, name);
|
||||
mesh_library_palette->set_item_metadata(item, id);
|
||||
const int list_id = mesh_library_palette->add_item("");
|
||||
|
||||
if (selected_palette == id) {
|
||||
mesh_library_palette->select(item);
|
||||
if (item_name.is_empty()) {
|
||||
item_name = "#" + itos(item_id);
|
||||
mesh_library_palette->set_item_tooltip(list_id, item_name);
|
||||
}
|
||||
mesh_library_palette->set_item_text(list_id, item_name);
|
||||
|
||||
const Ref<Texture2D> &preview = mesh_library->get_item_preview(item_id);
|
||||
if (preview.is_valid()) {
|
||||
mesh_library_palette->set_item_icon(list_id, preview);
|
||||
} else {
|
||||
Ref<Mesh> mesh = mesh_library->get_item_mesh(item_id);
|
||||
if (mesh.is_valid()) {
|
||||
// Fallback to the item's mesh preview.
|
||||
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(mesh, callable_mp(this, &GridMapEditor::_update_resource_preview).bind(list_id));
|
||||
}
|
||||
}
|
||||
|
||||
item++;
|
||||
mesh_library_palette->set_item_text(list_id, item_name);
|
||||
mesh_library_palette->set_item_metadata(list_id, item_id);
|
||||
}
|
||||
}
|
||||
|
||||
void GridMapEditor::_on_categories_item_activated() {
|
||||
TreeItem *ti_selected = categories->get_selected();
|
||||
if (ti_selected) {
|
||||
bool collapsed = ti_selected->is_collapsed();
|
||||
ti_selected->set_collapsed(!collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void GridMapEditor::_rebuild_categories() {
|
||||
ERR_FAIL_NULL(categories);
|
||||
|
||||
categories->clear();
|
||||
|
||||
if (mesh_library.is_null()) {
|
||||
categories->hide();
|
||||
item_palette_mc->set_theme_type_variation("NoBorderBottomPanel");
|
||||
mesh_library_palette->set_scroll_hint_mode(ItemList::SCROLL_HINT_MODE_BOTH);
|
||||
mesh_library_palette->set_theme_type_variation("");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<int> &item_ids = mesh_library->get_item_list();
|
||||
|
||||
categories->create_item();
|
||||
TreeItem *tree_root = categories->get_root();
|
||||
tree_root->set_text(0, TTRC("All"));
|
||||
tree_root->set_text(1, vformat("(%d)", item_ids.size()));
|
||||
tree_root->set_metadata(0, item_ids);
|
||||
|
||||
LocalVector<StringName> root_categories;
|
||||
AHashMap<StringName, HashSet<int>> category_to_items;
|
||||
AHashMap<StringName, HashSet<StringName>> category_to_category_children;
|
||||
|
||||
for (const int item_id : item_ids) {
|
||||
const StringName item_category = mesh_library->get_item_category(item_id);
|
||||
Vector<String> item_categories = String(item_category).split("/", false);
|
||||
|
||||
String item_category_path;
|
||||
for (int i = 0; i < item_categories.size(); i++) {
|
||||
const String category = item_categories[i];
|
||||
|
||||
if (i == 0 && !root_categories.has(category)) {
|
||||
root_categories.push_back(category);
|
||||
}
|
||||
|
||||
String parent_item_category_path = item_category_path;
|
||||
|
||||
item_category_path = item_category_path.path_join(category);
|
||||
|
||||
{
|
||||
AHashMap<StringName, HashSet<int>>::Iterator existing = category_to_items.find(item_category_path);
|
||||
if (!existing) {
|
||||
existing = category_to_items.insert(item_category_path, HashSet<int>());
|
||||
}
|
||||
existing->value.insert(item_id);
|
||||
}
|
||||
|
||||
{
|
||||
AHashMap<StringName, HashSet<StringName>>::Iterator existing = category_to_category_children.find(parent_item_category_path);
|
||||
if (!existing) {
|
||||
existing = category_to_category_children.insert(parent_item_category_path, HashSet<StringName>());
|
||||
}
|
||||
existing->value.insert(item_category_path);
|
||||
}
|
||||
{
|
||||
AHashMap<StringName, HashSet<StringName>>::Iterator existing = category_to_category_children.find(item_category_path);
|
||||
if (!existing) {
|
||||
category_to_category_children.insert(item_category_path, HashSet<StringName>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemCategoryMapping item_mapping;
|
||||
item_mapping.category_to_category_children = category_to_category_children;
|
||||
item_mapping.category_to_items = category_to_items;
|
||||
|
||||
for (const StringName &root_category : root_categories) {
|
||||
_add_child_categories_recursive(
|
||||
categories,
|
||||
tree_root,
|
||||
root_category,
|
||||
item_mapping);
|
||||
}
|
||||
|
||||
bool has_categories = !root_categories.is_empty();
|
||||
categories->set_visible(has_categories); // Only show the category tree if there are categories present.
|
||||
item_palette_mc->set_theme_type_variation(has_categories ? "" : "NoBorderBottomPanel");
|
||||
mesh_library_palette->set_scroll_hint_mode(has_categories ? ItemList::SCROLL_HINT_MODE_DISABLED : ItemList::SCROLL_HINT_MODE_BOTH);
|
||||
mesh_library_palette->set_theme_type_variation(has_categories ? "ItemListSecondary" : "");
|
||||
|
||||
update_palette();
|
||||
}
|
||||
|
||||
void GridMapEditor::_add_child_categories_recursive(Tree *p_item_tree, TreeItem *p_ti_parent, const StringName &p_category, const GridMapEditor::ItemCategoryMapping &p_mapping) {
|
||||
int category_user_count = 0;
|
||||
Vector<int> category_items_meta;
|
||||
|
||||
const AHashMap<StringName, HashSet<int>>::ConstIterator category_items_kv = p_mapping.category_to_items.find(p_category);
|
||||
if (category_items_kv) {
|
||||
const HashSet<int> &category_items = category_items_kv->value;
|
||||
category_user_count = category_items.size();
|
||||
|
||||
for (int item_id : category_items) {
|
||||
category_items_meta.push_back(item_id);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> subcategories = String(p_category).split("/", false);
|
||||
TreeItem *ti_category = p_item_tree->create_item(p_ti_parent);
|
||||
ti_category->set_collapsed(true);
|
||||
ti_category->set_text(0, vformat("%s", subcategories[subcategories.size() - 1]));
|
||||
ti_category->set_metadata(0, category_items_meta);
|
||||
ti_category->set_text(1, vformat("(%d)", category_user_count));
|
||||
|
||||
const AHashMap<StringName, HashSet<StringName>>::ConstIterator category_to_category_children_kv = p_mapping.category_to_category_children.find(p_category);
|
||||
if (category_to_category_children_kv) {
|
||||
const HashSet<StringName> &category_children = category_to_category_children_kv->value;
|
||||
|
||||
for (const StringName &category_child : category_children) {
|
||||
_add_child_categories_recursive(p_item_tree, ti_category, category_child, p_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1171,6 +1314,7 @@ void GridMapEditor::_update_mesh_library() {
|
||||
if (new_mesh_library != mesh_library) {
|
||||
if (mesh_library.is_valid()) {
|
||||
mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
|
||||
mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::_rebuild_categories));
|
||||
}
|
||||
mesh_library = new_mesh_library;
|
||||
} else {
|
||||
@@ -1180,6 +1324,7 @@ void GridMapEditor::_update_mesh_library() {
|
||||
MeshLibraryEditorPlugin *mesh_library_editor_plugin = MeshLibraryEditorPlugin::get_singleton();
|
||||
if (mesh_library.is_valid()) {
|
||||
mesh_library->connect_changed(callable_mp(this, &GridMapEditor::update_palette));
|
||||
mesh_library->connect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_rebuild_categories));
|
||||
|
||||
if (mesh_library_editor_plugin) {
|
||||
mesh_library_editor_plugin->edit(*mesh_library);
|
||||
@@ -1205,8 +1350,10 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
|
||||
if (node) {
|
||||
node->disconnect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
|
||||
node->disconnect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_update_mesh_library));
|
||||
node->disconnect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_rebuild_categories));
|
||||
if (mesh_library.is_valid()) {
|
||||
mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::update_palette));
|
||||
mesh_library->disconnect_changed(callable_mp(this, &GridMapEditor::_rebuild_categories));
|
||||
mesh_library = Ref<MeshLibrary>();
|
||||
}
|
||||
}
|
||||
@@ -1245,7 +1392,10 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
|
||||
|
||||
node->connect(SNAME("cell_size_changed"), callable_mp(this, &GridMapEditor::_draw_grids));
|
||||
node->connect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_update_mesh_library));
|
||||
node->connect(CoreStringName(changed), callable_mp(this, &GridMapEditor::_rebuild_categories));
|
||||
|
||||
_update_mesh_library();
|
||||
_rebuild_categories();
|
||||
}
|
||||
|
||||
void GridMapEditor::update_grid() {
|
||||
@@ -1387,6 +1537,11 @@ void GridMapEditor::_notification(int p_what) {
|
||||
vformat(TTR("Change Grid Floor:\nPrevious Plane (%s)\nNext Plane (%s)"),
|
||||
ED_GET_SHORTCUT("grid_map/previous_floor")->get_as_text(),
|
||||
ED_GET_SHORTCUT("grid_map/next_floor")->get_as_text()));
|
||||
|
||||
TreeItem *tree_root = categories->get_root();
|
||||
if (tree_root) {
|
||||
tree_root->set_text(0, TTRC("All"));
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
|
||||
@@ -1502,7 +1657,6 @@ GridMapEditor::GridMapEditor() {
|
||||
set_global(false);
|
||||
set_transient(true);
|
||||
set_custom_minimum_size(Size2(0, 200 * EDSCALE));
|
||||
set_theme_type_variation("NoBorderBottomPanel");
|
||||
|
||||
ED_SHORTCUT("grid_map/previous_floor", TTRC("Previous Floor"), Key::KEY_1, true);
|
||||
ED_SHORTCUT("grid_map/next_floor", TTRC("Next Floor"), Key::KEY_3, true);
|
||||
@@ -1512,25 +1666,11 @@ GridMapEditor::GridMapEditor() {
|
||||
ED_SHORTCUT("grid_map/keep_selected", TTRC("Keep Selection"));
|
||||
ED_SHORTCUT("grid_map/clear_rotation", TTRC("Clear Rotation"), KeyModifierMask::ALT | Key::G, true);
|
||||
|
||||
options = memnew(MenuButton);
|
||||
options->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS);
|
||||
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS);
|
||||
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS);
|
||||
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true);
|
||||
options->get_popup()->add_separator();
|
||||
// TRANSLATORS: This is a toggle to select after pasting the new content.
|
||||
options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
|
||||
options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/keep_selected"), MENU_OPTION_PASTE_SELECTS);
|
||||
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS), true);
|
||||
options->get_popup()->add_separator();
|
||||
options->get_popup()->add_item(TTRC("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS);
|
||||
|
||||
settings_dialog = memnew(ConfirmationDialog);
|
||||
settings_dialog->set_title(TTRC("GridMap Settings"));
|
||||
add_child(settings_dialog);
|
||||
settings_vbc = memnew(VBoxContainer);
|
||||
settings_vbc->set_custom_minimum_size(Size2(200, 0) * EDSCALE);
|
||||
settings_vbc->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
|
||||
settings_dialog->add_child(settings_vbc);
|
||||
|
||||
settings_pick_distance = memnew(SpinBox);
|
||||
@@ -1541,8 +1681,6 @@ GridMapEditor::GridMapEditor() {
|
||||
settings_pick_distance->set_accessibility_name(TTRC("Pick Distance:"));
|
||||
settings_vbc->add_margin_child(TTRC("Pick Distance:"), settings_pick_distance);
|
||||
|
||||
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GridMapEditor::_menu_option));
|
||||
|
||||
VBoxContainer *main_vb = memnew(VBoxContainer);
|
||||
add_child(main_vb);
|
||||
|
||||
@@ -1751,14 +1889,52 @@ GridMapEditor::GridMapEditor() {
|
||||
toolbar->add_child(mode_list);
|
||||
mode_list->connect(SceneStringName(pressed), callable_mp(this, &GridMapEditor::_set_display_mode).bind(DISPLAY_LIST));
|
||||
|
||||
options = memnew(MenuButton);
|
||||
options->set_theme_type_variation(SceneStringName(FlatButton));
|
||||
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_x_axis"), MENU_OPTION_X_AXIS);
|
||||
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_y_axis"), MENU_OPTION_Y_AXIS);
|
||||
options->get_popup()->add_radio_check_shortcut(ED_GET_SHORTCUT("grid_map/edit_z_axis"), MENU_OPTION_Z_AXIS);
|
||||
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_Y_AXIS), true);
|
||||
options->get_popup()->add_separator();
|
||||
// TRANSLATORS: This is a toggle to select after pasting the new content.
|
||||
options->get_popup()->add_shortcut(ED_GET_SHORTCUT("grid_map/clear_rotation"), MENU_OPTION_CURSOR_CLEAR_ROTATION);
|
||||
options->get_popup()->add_check_shortcut(ED_GET_SHORTCUT("grid_map/keep_selected"), MENU_OPTION_PASTE_SELECTS);
|
||||
options->get_popup()->set_item_checked(options->get_popup()->get_item_index(MENU_OPTION_PASTE_SELECTS), true);
|
||||
options->get_popup()->add_separator();
|
||||
options->get_popup()->add_item(TTRC("Settings..."), MENU_OPTION_GRIDMAP_SETTINGS);
|
||||
options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &GridMapEditor::_menu_option));
|
||||
toolbar->add_child(options);
|
||||
|
||||
HSplitContainer *hsplit = memnew(HSplitContainer);
|
||||
hsplit->set_split_offset(256 * EDSCALE);
|
||||
hsplit->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
hsplit->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
main_vb->add_child(hsplit);
|
||||
|
||||
categories = memnew(Tree);
|
||||
categories->set_select_mode(Tree::SelectMode::SELECT_ROW);
|
||||
categories->set_columns(2);
|
||||
categories->set_column_expand(0, true);
|
||||
categories->set_column_expand(1, false);
|
||||
categories->set_column_custom_minimum_width(1, 64 * EDSCALE);
|
||||
categories->set_hide_root(false);
|
||||
categories->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
categories->set_theme_type_variation("TreeSecondary");
|
||||
categories->hide();
|
||||
hsplit->add_child(categories);
|
||||
categories->connect(SNAME("item_activated"), callable_mp(this, &GridMapEditor::_on_categories_item_activated));
|
||||
categories->connect(SceneStringName(item_selected), callable_mp(this, &GridMapEditor::update_palette));
|
||||
|
||||
item_palette_mc = memnew(MarginContainer);
|
||||
item_palette_mc->set_theme_type_variation("NoBorderHorizontal");
|
||||
hsplit->add_child(item_palette_mc);
|
||||
|
||||
mesh_library_palette = memnew(ItemList);
|
||||
search_box->set_forward_control(mesh_library_palette);
|
||||
mesh_library_palette->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
mesh_library_palette->set_scroll_hint_mode(ItemList::SCROLL_HINT_MODE_BOTH);
|
||||
mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
main_vb->add_child(mesh_library_palette);
|
||||
mesh_library_palette->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
|
||||
item_palette_mc->add_child(mesh_library_palette);
|
||||
search_box->set_forward_control(mesh_library_palette);
|
||||
mesh_library_palette->connect(SceneStringName(gui_input), callable_mp(this, &GridMapEditor::_mesh_library_palette_input));
|
||||
mesh_library_palette->connect(SceneStringName(item_selected), callable_mp(this, &GridMapEditor::_item_selected_cbk));
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ class ButtonGroup;
|
||||
class EditorZoomWidget;
|
||||
class BaseButton;
|
||||
class SpinBox;
|
||||
class Tree;
|
||||
class TreeItem;
|
||||
|
||||
class GridMapEditor : public EditorDock {
|
||||
GDCLASS(GridMapEditor, EditorDock);
|
||||
@@ -216,6 +218,8 @@ class GridMapEditor : public EditorDock {
|
||||
RID instance;
|
||||
};
|
||||
|
||||
Tree *categories = nullptr;
|
||||
MarginContainer *item_palette_mc = nullptr;
|
||||
ItemList *mesh_library_palette = nullptr;
|
||||
Label *info_message = nullptr;
|
||||
|
||||
@@ -266,6 +270,16 @@ class GridMapEditor : public EditorDock {
|
||||
|
||||
friend class GridMapEditorPlugin;
|
||||
|
||||
struct ItemCategoryMapping {
|
||||
AHashMap<StringName, HashSet<StringName>> category_to_category_children;
|
||||
AHashMap<StringName, HashSet<int>> category_to_items;
|
||||
};
|
||||
|
||||
void _on_categories_item_activated();
|
||||
|
||||
void _rebuild_categories();
|
||||
void _add_child_categories_recursive(Tree *p_categories, TreeItem *p_ti_parent, const StringName &p_category, const ItemCategoryMapping &p_mapping);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String prop_name = p_name;
|
||||
|
||||
if (prop_name.begins_with("item/")) {
|
||||
int idx = prop_name.get_slicec('/', 1).to_int();
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
@@ -49,6 +50,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) {
|
||||
|
||||
if (what == "name") {
|
||||
set_item_name(idx, p_value);
|
||||
} else if (what == "category") {
|
||||
set_item_category(idx, p_value);
|
||||
} else if (what == "mesh") {
|
||||
set_item_mesh(idx, p_value);
|
||||
} else if (what == "mesh_transform") {
|
||||
@@ -98,45 +101,52 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
String prop_name = p_name;
|
||||
int idx = prop_name.get_slicec('/', 1).to_int();
|
||||
ERR_FAIL_COND_V(!item_map.has(idx), false);
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
|
||||
if (what == "name") {
|
||||
r_ret = get_item_name(idx);
|
||||
} else if (what == "mesh") {
|
||||
r_ret = get_item_mesh(idx);
|
||||
} else if (what == "mesh_transform") {
|
||||
r_ret = get_item_mesh_transform(idx);
|
||||
} else if (what == "mesh_cast_shadow") {
|
||||
r_ret = (int)get_item_mesh_cast_shadow(idx);
|
||||
if (prop_name.begins_with("item/")) {
|
||||
int idx = prop_name.get_slicec('/', 1).to_int();
|
||||
ERR_FAIL_COND_V(!item_map.has(idx), false);
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
|
||||
if (what == "name") {
|
||||
r_ret = get_item_name(idx);
|
||||
} else if (what == "category") {
|
||||
r_ret = get_item_category(idx);
|
||||
} else if (what == "mesh") {
|
||||
r_ret = get_item_mesh(idx);
|
||||
} else if (what == "mesh_transform") {
|
||||
r_ret = get_item_mesh_transform(idx);
|
||||
} else if (what == "mesh_cast_shadow") {
|
||||
r_ret = (int)get_item_mesh_cast_shadow(idx);
|
||||
#ifndef PHYSICS_3D_DISABLED
|
||||
} else if (what == "shapes") {
|
||||
r_ret = _get_item_shapes(idx);
|
||||
} else if (what == "shapes") {
|
||||
r_ret = _get_item_shapes(idx);
|
||||
#endif // PHYSICS_3D_DISABLED
|
||||
} else if (what == "navigation_mesh") {
|
||||
r_ret = get_item_navigation_mesh(idx);
|
||||
} else if (what == "navigation_mesh_transform") {
|
||||
r_ret = get_item_navigation_mesh_transform(idx);
|
||||
} else if (what == "navigation_mesh") {
|
||||
r_ret = get_item_navigation_mesh(idx);
|
||||
} else if (what == "navigation_mesh_transform") {
|
||||
r_ret = get_item_navigation_mesh_transform(idx);
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
} else if (what == "navmesh") { // Renamed in 4.0 beta 9.
|
||||
r_ret = get_item_navigation_mesh(idx);
|
||||
} else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9.
|
||||
r_ret = get_item_navigation_mesh_transform(idx);
|
||||
} else if (what == "navmesh") { // Renamed in 4.0 beta 9.
|
||||
r_ret = get_item_navigation_mesh(idx);
|
||||
} else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9.
|
||||
r_ret = get_item_navigation_mesh_transform(idx);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
} else if (what == "navigation_layers") {
|
||||
r_ret = get_item_navigation_layers(idx);
|
||||
} else if (what == "preview") {
|
||||
r_ret = get_item_preview(idx);
|
||||
} else if (what == "navigation_layers") {
|
||||
r_ret = get_item_navigation_layers(idx);
|
||||
} else if (what == "preview") {
|
||||
r_ret = get_item_preview(idx);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -148,6 +158,7 @@ void MeshLibrary::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (const KeyValue<int, Item> &E : item_map) {
|
||||
String prop_name = vformat("%s/%d/", PNAME("item"), E.key);
|
||||
p_list->push_back(PropertyInfo(Variant::STRING, prop_name + PNAME("name"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::STRING_NAME, prop_name + PNAME("category"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, Mesh::get_class_static(), PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m", PROPERTY_USAGE_NO_EDITOR));
|
||||
p_list->push_back(PropertyInfo(Variant::INT, prop_name + PNAME("mesh_cast_shadow"), PROPERTY_HINT_ENUM, "Off,On,Double-Sided,Shadows Only", PROPERTY_USAGE_NO_EDITOR));
|
||||
@@ -173,6 +184,12 @@ void MeshLibrary::set_item_name(int p_item, const String &p_name) {
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
void MeshLibrary::set_item_category(int p_item, const StringName &p_category) {
|
||||
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
|
||||
item_map[p_item].category = p_category;
|
||||
emit_changed();
|
||||
}
|
||||
|
||||
void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) {
|
||||
ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
|
||||
item_map[p_item].mesh = p_mesh;
|
||||
@@ -229,6 +246,11 @@ String MeshLibrary::get_item_name(int p_item) const {
|
||||
return item_map[p_item].name;
|
||||
}
|
||||
|
||||
StringName MeshLibrary::get_item_category(int p_item) const {
|
||||
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), StringName(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
|
||||
return item_map[p_item].category;
|
||||
}
|
||||
|
||||
Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const {
|
||||
ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref<Mesh>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'.");
|
||||
return item_map[p_item].mesh;
|
||||
@@ -371,9 +393,11 @@ Array MeshLibrary::_get_item_shapes(int p_item) const {
|
||||
void MeshLibrary::reset_state() {
|
||||
clear();
|
||||
}
|
||||
|
||||
void MeshLibrary::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item);
|
||||
ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name);
|
||||
ClassDB::bind_method(D_METHOD("set_item_category", "id", "category"), &MeshLibrary::set_item_category);
|
||||
ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh);
|
||||
ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform);
|
||||
ClassDB::bind_method(D_METHOD("set_item_mesh_cast_shadow", "id", "shadow_casting_setting"), &MeshLibrary::set_item_mesh_cast_shadow);
|
||||
@@ -385,6 +409,7 @@ void MeshLibrary::_bind_methods() {
|
||||
#endif // PHYSICS_3D_DISABLED
|
||||
ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview);
|
||||
ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name);
|
||||
ClassDB::bind_method(D_METHOD("get_item_category", "id"), &MeshLibrary::get_item_category);
|
||||
ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh);
|
||||
ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform);
|
||||
ClassDB::bind_method(D_METHOD("get_item_mesh_cast_shadow", "id"), &MeshLibrary::get_item_mesh_cast_shadow);
|
||||
@@ -403,9 +428,3 @@ void MeshLibrary::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_item_count"), &MeshLibrary::get_item_count);
|
||||
ClassDB::bind_method(D_METHOD("get_last_unused_item_id"), &MeshLibrary::get_last_unused_item_id);
|
||||
}
|
||||
|
||||
MeshLibrary::MeshLibrary() {
|
||||
}
|
||||
|
||||
MeshLibrary::~MeshLibrary() {
|
||||
}
|
||||
|
||||
@@ -51,8 +51,10 @@ public:
|
||||
Transform3D local_transform;
|
||||
};
|
||||
#endif // PHYSICS_3D_DISABLED
|
||||
|
||||
struct Item {
|
||||
String name;
|
||||
StringName category;
|
||||
Ref<Mesh> mesh;
|
||||
Transform3D mesh_transform;
|
||||
RSE::ShadowCastingSetting mesh_cast_shadow = RSE::SHADOW_CASTING_SETTING_ON;
|
||||
@@ -83,6 +85,7 @@ protected:
|
||||
public:
|
||||
void create_item(int p_item);
|
||||
void set_item_name(int p_item, const String &p_name);
|
||||
void set_item_category(int p_item, const StringName &p_category);
|
||||
void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh);
|
||||
void set_item_mesh_transform(int p_item, const Transform3D &p_transform);
|
||||
void set_item_mesh_cast_shadow(int p_item, RSE::ShadowCastingSetting p_shadow_casting_setting);
|
||||
@@ -94,6 +97,7 @@ public:
|
||||
#endif // PHYSICS_3D_DISABLED
|
||||
void set_item_preview(int p_item, const Ref<Texture2D> &p_preview);
|
||||
String get_item_name(int p_item) const;
|
||||
StringName get_item_category(int p_item) const;
|
||||
Ref<Mesh> get_item_mesh(int p_item) const;
|
||||
Transform3D get_item_mesh_transform(int p_item) const;
|
||||
RSE::ShadowCastingSetting get_item_mesh_cast_shadow(int p_item) const;
|
||||
@@ -115,7 +119,4 @@ public:
|
||||
Vector<int> get_item_list() const;
|
||||
int get_item_count() const { return item_map.size(); }
|
||||
int get_last_unused_item_id() const;
|
||||
|
||||
MeshLibrary();
|
||||
~MeshLibrary();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user