Merge pull request #75047 from KoBeWi/#new_#feature_#tags_#much_#wow
Add project tags
This commit is contained in:
commit
f6db010f5d
4 changed files with 409 additions and 17 deletions
|
@ -1251,6 +1251,7 @@ ProjectSettings::ProjectSettings() {
|
|||
GLOBAL_DEF_BASIC("application/config/name", "");
|
||||
GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary());
|
||||
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), "");
|
||||
GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray());
|
||||
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/run/main_scene", PROPERTY_HINT_FILE, "*.tscn,*.scn,*.res"), "");
|
||||
GLOBAL_DEF("application/run/disable_stdout", false);
|
||||
GLOBAL_DEF("application/run/disable_stderr", false);
|
||||
|
|
|
@ -939,6 +939,33 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
editor_log_button_pressed->set_border_color(accent_color);
|
||||
theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed);
|
||||
|
||||
// ProjectTag
|
||||
{
|
||||
theme->set_type_variation("ProjectTag", "Button");
|
||||
|
||||
Ref<StyleBoxFlat> tag = style_widget->duplicate();
|
||||
tag->set_bg_color(dark_theme ? tag->get_bg_color().lightened(0.2) : tag->get_bg_color().darkened(0.2));
|
||||
tag->set_corner_radius(CORNER_TOP_LEFT, 0);
|
||||
tag->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
|
||||
tag->set_corner_radius(CORNER_TOP_RIGHT, 4);
|
||||
tag->set_corner_radius(CORNER_BOTTOM_RIGHT, 4);
|
||||
theme->set_stylebox("normal", "ProjectTag", tag);
|
||||
|
||||
tag = style_widget_hover->duplicate();
|
||||
tag->set_corner_radius(CORNER_TOP_LEFT, 0);
|
||||
tag->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
|
||||
tag->set_corner_radius(CORNER_TOP_RIGHT, 4);
|
||||
tag->set_corner_radius(CORNER_BOTTOM_RIGHT, 4);
|
||||
theme->set_stylebox("hover", "ProjectTag", tag);
|
||||
|
||||
tag = style_widget_pressed->duplicate();
|
||||
tag->set_corner_radius(CORNER_TOP_LEFT, 0);
|
||||
tag->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
|
||||
tag->set_corner_radius(CORNER_TOP_RIGHT, 4);
|
||||
tag->set_corner_radius(CORNER_BOTTOM_RIGHT, 4);
|
||||
theme->set_stylebox("pressed", "ProjectTag", tag);
|
||||
}
|
||||
|
||||
// MenuBar
|
||||
theme->set_stylebox("normal", "MenuBar", style_widget);
|
||||
theme->set_stylebox("hover", "MenuBar", style_widget_hover);
|
||||
|
|
|
@ -51,6 +51,8 @@
|
|||
#include "main/main.h"
|
||||
#include "scene/gui/center_container.h"
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/color_rect.h"
|
||||
#include "scene/gui/flow_container.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
|
@ -979,8 +981,7 @@ void ProjectListItemControl::_notification(int p_what) {
|
|||
project_title->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("title_size"), SNAME("EditorFonts")));
|
||||
project_title->add_theme_color_override("font_color", get_theme_color(SNAME("font_color"), SNAME("Tree")));
|
||||
project_path->add_theme_color_override("font_color", get_theme_color(SNAME("font_color"), SNAME("Tree")));
|
||||
project_unsupported_features->add_theme_font_override("font", get_theme_font(SNAME("title"), SNAME("EditorFonts")));
|
||||
project_unsupported_features->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor")));
|
||||
project_unsupported_features->set_texture(get_theme_icon(SNAME("NodeWarning"), SNAME("EditorIcons")));
|
||||
|
||||
favorite_button->set_texture_normal(get_theme_icon(SNAME("Favorites"), SNAME("EditorIcons")));
|
||||
if (project_is_missing) {
|
||||
|
@ -1021,6 +1022,14 @@ void ProjectListItemControl::set_project_path(const String &p_path) {
|
|||
project_path->set_text(p_path);
|
||||
}
|
||||
|
||||
void ProjectListItemControl::set_tags(const PackedStringArray &p_tags, ProjectList *p_parent_list) {
|
||||
for (const String &tag : p_tags) {
|
||||
ProjectTag *tag_control = memnew(ProjectTag(tag));
|
||||
tag_container->add_child(tag_control);
|
||||
tag_control->connect_button_to(callable_mp(p_parent_list, &ProjectList::add_search_tag).bind(tag));
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectListItemControl::set_project_icon(const Ref<Texture2D> &p_icon) {
|
||||
icon_needs_reload = false;
|
||||
|
||||
|
@ -1036,8 +1045,7 @@ void ProjectListItemControl::set_project_icon(const Ref<Texture2D> &p_icon) {
|
|||
void ProjectListItemControl::set_unsupported_features(const PackedStringArray &p_features) {
|
||||
if (p_features.size() > 0) {
|
||||
String unsupported_features_str = String(", ").join(p_features);
|
||||
project_unsupported_features->set_text(unsupported_features_str);
|
||||
project_unsupported_features->set_custom_minimum_size(Size2(unsupported_features_str.length() * 15, 10) * EDSCALE);
|
||||
project_unsupported_features->set_tooltip_text(TTR("The project uses features unsupported by the current build:") + "\n" + unsupported_features_str);
|
||||
project_unsupported_features->show();
|
||||
} else {
|
||||
project_unsupported_features->hide();
|
||||
|
@ -1133,7 +1141,7 @@ ProjectListItemControl::ProjectListItemControl() {
|
|||
ec->set_mouse_filter(MOUSE_FILTER_PASS);
|
||||
main_vbox->add_child(ec);
|
||||
|
||||
// Top half, title and unsupported features labels.
|
||||
// Top half, title, tags and unsupported features labels.
|
||||
{
|
||||
HBoxContainer *title_hb = memnew(HBoxContainer);
|
||||
main_vbox->add_child(title_hb);
|
||||
|
@ -1144,12 +1152,8 @@ ProjectListItemControl::ProjectListItemControl() {
|
|||
project_title->set_clip_text(true);
|
||||
title_hb->add_child(project_title);
|
||||
|
||||
project_unsupported_features = memnew(Label);
|
||||
project_unsupported_features->set_name("ProjectUnsupportedFeatures");
|
||||
project_unsupported_features->set_clip_text(true);
|
||||
project_unsupported_features->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
title_hb->add_child(project_unsupported_features);
|
||||
project_unsupported_features->hide();
|
||||
tag_container = memnew(HBoxContainer);
|
||||
title_hb->add_child(tag_container);
|
||||
|
||||
Control *spacer = memnew(Control);
|
||||
spacer->set_custom_minimum_size(Size2(10, 10));
|
||||
|
@ -1175,6 +1179,16 @@ ProjectListItemControl::ProjectListItemControl() {
|
|||
project_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
project_path->set_modulate(Color(1, 1, 1, 0.5));
|
||||
path_hb->add_child(project_path);
|
||||
|
||||
project_unsupported_features = memnew(TextureRect);
|
||||
project_unsupported_features->set_name("ProjectUnsupportedFeatures");
|
||||
project_unsupported_features->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
|
||||
path_hb->add_child(project_unsupported_features);
|
||||
project_unsupported_features->hide();
|
||||
|
||||
Control *spacer = memnew(Control);
|
||||
spacer->set_custom_minimum_size(Size2(10, 10));
|
||||
path_hb->add_child(spacer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1194,6 +1208,8 @@ struct ProjectListComparator {
|
|||
return a.path < b.path;
|
||||
case ProjectList::EDIT_DATE:
|
||||
return a.last_edited > b.last_edited;
|
||||
case ProjectList::TAGS:
|
||||
return a.tag_sort_string < b.tag_sort_string;
|
||||
default:
|
||||
return a.project_name < b.project_name;
|
||||
}
|
||||
|
@ -1260,7 +1276,7 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
|
|||
int config_version = 0;
|
||||
String project_name = TTR("Unnamed Project");
|
||||
if (cf_err == OK) {
|
||||
String cf_project_name = static_cast<String>(cf->get_value("application", "config/name", ""));
|
||||
String cf_project_name = cf->get_value("application", "config/name", "");
|
||||
if (!cf_project_name.is_empty()) {
|
||||
project_name = cf_project_name.xml_unescape();
|
||||
}
|
||||
|
@ -1273,6 +1289,7 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
|
|||
}
|
||||
|
||||
const String description = cf->get_value("application", "config/description", "");
|
||||
const PackedStringArray tags = cf->get_value("application", "config/tags", PackedStringArray());
|
||||
const String icon = cf->get_value("application", "config/icon", "");
|
||||
const String main_scene = cf->get_value("application", "run/main_scene", "");
|
||||
|
||||
|
@ -1299,7 +1316,11 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
|
|||
print_line("Project is missing: " + conf);
|
||||
}
|
||||
|
||||
return Item(project_name, description, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
|
||||
for (const String &tag : tags) {
|
||||
ProjectManager::get_singleton()->add_new_tag(tag);
|
||||
}
|
||||
|
||||
return Item(project_name, description, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
|
||||
}
|
||||
|
||||
void ProjectList::migrate_config() {
|
||||
|
@ -1427,6 +1448,7 @@ void ProjectList::_create_project_item_control(int p_index) {
|
|||
hb->set_project_title(!item.missing ? item.project_name : TTR("Missing Project"));
|
||||
hb->set_project_path(item.path);
|
||||
hb->set_tooltip_text(item.description);
|
||||
hb->set_tags(item.tags, this);
|
||||
hb->set_unsupported_features(item.unsupported_features);
|
||||
|
||||
hb->set_is_favorite(item.favorite);
|
||||
|
@ -1462,13 +1484,33 @@ void ProjectList::sort_projects() {
|
|||
sorter.compare.order_option = _order_option;
|
||||
sorter.sort(_projects.ptrw(), _projects.size());
|
||||
|
||||
String search_term;
|
||||
PackedStringArray tags;
|
||||
|
||||
if (!_search_term.is_empty()) {
|
||||
PackedStringArray search_parts = _search_term.split(" ");
|
||||
if (search_parts.size() > 1 || search_parts[0].begins_with("tag:")) {
|
||||
PackedStringArray remaining;
|
||||
for (const String &part : search_parts) {
|
||||
if (part.begins_with("tag:")) {
|
||||
tags.push_back(part.get_slice(":", 1));
|
||||
} else {
|
||||
remaining.append(part);
|
||||
}
|
||||
}
|
||||
search_term = String(" ").join(remaining); // Search term without tags.
|
||||
} else {
|
||||
search_term = _search_term;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _projects.size(); ++i) {
|
||||
Item &item = _projects.write[i];
|
||||
|
||||
bool item_visible = true;
|
||||
if (!_search_term.is_empty()) {
|
||||
String search_path;
|
||||
if (_search_term.contains("/")) {
|
||||
if (search_term.contains("/")) {
|
||||
// Search path will match the whole path
|
||||
search_path = item.path;
|
||||
} else {
|
||||
|
@ -1476,8 +1518,16 @@ void ProjectList::sort_projects() {
|
|||
search_path = item.path.get_file();
|
||||
}
|
||||
|
||||
// When searching, display projects whose name or path contain the search term
|
||||
item_visible = item.project_name.findn(_search_term) != -1 || search_path.findn(_search_term) != -1;
|
||||
bool missing_tags = false;
|
||||
for (const String &tag : tags) {
|
||||
if (!item.tags.has(tag)) {
|
||||
missing_tags = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// When searching, display projects whose name or path contain the search term and whose tags match the searched tags.
|
||||
item_visible = !missing_tags && (search_term.is_empty() || item.project_name.findn(search_term) != -1 || search_path.findn(search_term) != -1);
|
||||
}
|
||||
|
||||
item.control->set_visible(item_visible);
|
||||
|
@ -1877,8 +1927,13 @@ void ProjectManager::_notification(int p_what) {
|
|||
open_btn->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")));
|
||||
run_btn->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
|
||||
rename_btn->set_icon(get_theme_icon(SNAME("Rename"), SNAME("EditorIcons")));
|
||||
manage_tags_btn->set_icon(get_theme_icon("Script", "EditorIcons"));
|
||||
erase_btn->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")));
|
||||
erase_missing_btn->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")));
|
||||
create_tag_btn->set_icon(get_theme_icon("Add", "EditorIcons"));
|
||||
|
||||
tag_error->add_theme_color_override("font_color", get_theme_color("error_color", "Editor"));
|
||||
tag_edit_error->add_theme_color_override("font_color", get_theme_color("error_color", "Editor"));
|
||||
|
||||
create_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
import_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
|
@ -1886,6 +1941,7 @@ void ProjectManager::_notification(int p_what) {
|
|||
open_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
run_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
rename_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
manage_tags_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
erase_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
erase_missing_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
|
||||
|
||||
|
@ -1999,6 +2055,7 @@ void ProjectManager::_update_project_buttons() {
|
|||
erase_btn->set_disabled(empty_selection);
|
||||
open_btn->set_disabled(empty_selection || is_missing_project_selected);
|
||||
rename_btn->set_disabled(empty_selection || is_missing_project_selected);
|
||||
manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1);
|
||||
run_btn->set_disabled(empty_selection || is_missing_project_selected);
|
||||
|
||||
erase_missing_btn->set_disabled(!_project_list->is_any_project_missing());
|
||||
|
@ -2389,6 +2446,115 @@ void ProjectManager::_rename_project() {
|
|||
}
|
||||
}
|
||||
|
||||
void ProjectManager::_manage_project_tags() {
|
||||
for (int i = 0; i < project_tags->get_child_count(); i++) {
|
||||
project_tags->get_child(i)->queue_free();
|
||||
}
|
||||
|
||||
const ProjectList::Item item = _project_list->get_selected_projects()[0];
|
||||
current_project_tags = item.tags;
|
||||
for (const String &tag : current_project_tags) {
|
||||
ProjectTag *tag_control = memnew(ProjectTag(tag, true));
|
||||
project_tags->add_child(tag_control);
|
||||
tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(tag));
|
||||
}
|
||||
|
||||
tag_edit_error->hide();
|
||||
tag_manage_dialog->popup_centered(Vector2i(500, 0) * EDSCALE);
|
||||
}
|
||||
|
||||
void ProjectManager::_add_project_tag(const String &p_tag) {
|
||||
if (current_project_tags.has(p_tag)) {
|
||||
return;
|
||||
}
|
||||
current_project_tags.append(p_tag);
|
||||
|
||||
ProjectTag *tag_control = memnew(ProjectTag(p_tag, true));
|
||||
project_tags->add_child(tag_control);
|
||||
tag_control->connect_button_to(callable_mp(this, &ProjectManager::_delete_project_tag).bind(p_tag));
|
||||
}
|
||||
|
||||
void ProjectManager::_delete_project_tag(const String &p_tag) {
|
||||
current_project_tags.erase(p_tag);
|
||||
for (int i = 0; i < project_tags->get_child_count(); i++) {
|
||||
ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));
|
||||
if (tag_control && tag_control->get_tag() == p_tag) {
|
||||
memdelete(tag_control);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::_apply_project_tags() {
|
||||
ProjectList::Item &item = _project_list->get_selected_projects().write[0];
|
||||
|
||||
PackedStringArray tags;
|
||||
for (int i = 0; i < project_tags->get_child_count(); i++) {
|
||||
ProjectTag *tag_control = Object::cast_to<ProjectTag>(project_tags->get_child(i));
|
||||
if (tag_control) {
|
||||
tags.append(tag_control->get_tag());
|
||||
}
|
||||
}
|
||||
|
||||
ConfigFile cfg;
|
||||
String project_godot = item.path.path_join("project.godot");
|
||||
Error err = cfg.load(project_godot);
|
||||
if (err != OK) {
|
||||
tag_edit_error->set_text(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err));
|
||||
tag_edit_error->show();
|
||||
callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred(); // Make sure the dialog does not disappear.
|
||||
return;
|
||||
} else {
|
||||
cfg.set_value("application", "config/tags", tags);
|
||||
err = cfg.save(project_godot);
|
||||
if (err != OK) {
|
||||
tag_edit_error->set_text(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err));
|
||||
tag_edit_error->show();
|
||||
callable_mp((Window *)tag_manage_dialog, &Window::show).call_deferred();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_on_projects_updated();
|
||||
}
|
||||
|
||||
void ProjectManager::_set_new_tag_name(const String p_name) {
|
||||
create_tag_dialog->get_ok_button()->set_disabled(true);
|
||||
if (p_name.is_empty()) {
|
||||
tag_error->set_text(TTR("Tag name can't be empty."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_name.contains(" ")) {
|
||||
tag_error->set_text(TTR("Tag name can't contain spaces."));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const String &c : forbidden_tag_characters) {
|
||||
if (p_name.contains(c)) {
|
||||
tag_error->set_text(vformat(TTR("These characters are not allowed in tags: %s."), String(" ").join(forbidden_tag_characters)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_name.to_lower() != p_name) {
|
||||
tag_error->set_text(TTR("Tag name must be lowercase."));
|
||||
return;
|
||||
}
|
||||
|
||||
tag_error->set_text("");
|
||||
create_tag_dialog->get_ok_button()->set_disabled(false);
|
||||
}
|
||||
|
||||
void ProjectManager::_create_new_tag() {
|
||||
if (!tag_error->get_text().is_empty()) {
|
||||
return;
|
||||
}
|
||||
create_tag_dialog->hide(); // When using text_submitted, need to hide manually.
|
||||
add_new_tag(new_tag_name->get_text());
|
||||
_add_project_tag(new_tag_name->get_text());
|
||||
}
|
||||
|
||||
void ProjectManager::_erase_project_confirm() {
|
||||
_project_list->erase_selected_projects(false);
|
||||
_update_project_buttons();
|
||||
|
@ -2546,6 +2712,36 @@ void ProjectManager::_version_button_pressed() {
|
|||
DisplayServer::get_singleton()->clipboard_set(version_btn->get_text());
|
||||
}
|
||||
|
||||
LineEdit *ProjectManager::get_search_box() {
|
||||
return search_box;
|
||||
}
|
||||
|
||||
void ProjectManager::add_new_tag(const String &p_tag) {
|
||||
if (!tag_set.has(p_tag)) {
|
||||
tag_set.insert(p_tag);
|
||||
ProjectTag *tag_control = memnew(ProjectTag(p_tag));
|
||||
all_tags->add_child(tag_control);
|
||||
all_tags->move_child(tag_control, -2);
|
||||
tag_control->connect_button_to(callable_mp(this, &ProjectManager::_add_project_tag).bind(p_tag));
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectList::add_search_tag(const String &p_tag) {
|
||||
const String tag_string = "tag:" + p_tag;
|
||||
|
||||
int exists = _search_term.find(tag_string);
|
||||
if (exists > -1) {
|
||||
_search_term = _search_term.erase(exists, tag_string.length() + 1);
|
||||
} else if (_search_term.is_empty() || _search_term.ends_with(" ")) {
|
||||
_search_term += tag_string;
|
||||
} else {
|
||||
_search_term += " " + tag_string;
|
||||
}
|
||||
ProjectManager::get_singleton()->get_search_box()->set_text(_search_term);
|
||||
|
||||
sort_projects();
|
||||
}
|
||||
|
||||
ProjectManager::ProjectManager() {
|
||||
singleton = this;
|
||||
|
||||
|
@ -2673,6 +2869,7 @@ ProjectManager::ProjectManager() {
|
|||
sort_filter_titles.push_back(TTR("Last Edited"));
|
||||
sort_filter_titles.push_back(TTR("Name"));
|
||||
sort_filter_titles.push_back(TTR("Path"));
|
||||
sort_filter_titles.push_back(TTR("Tags"));
|
||||
|
||||
for (int i = 0; i < sort_filter_titles.size(); i++) {
|
||||
filter_option->add_item(sort_filter_titles[i]);
|
||||
|
@ -2734,6 +2931,10 @@ ProjectManager::ProjectManager() {
|
|||
rename_btn->connect("pressed", callable_mp(this, &ProjectManager::_rename_project));
|
||||
tree_vb->add_child(rename_btn);
|
||||
|
||||
manage_tags_btn = memnew(Button);
|
||||
manage_tags_btn->set_text(TTR("Manage Tags"));
|
||||
tree_vb->add_child(manage_tags_btn);
|
||||
|
||||
erase_btn = memnew(Button);
|
||||
erase_btn->set_text(TTR("Remove"));
|
||||
erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTR("Remove Project"), Key::KEY_DELETE));
|
||||
|
@ -2925,6 +3126,75 @@ ProjectManager::ProjectManager() {
|
|||
_build_icon_type_cache(get_theme());
|
||||
}
|
||||
|
||||
{
|
||||
// Tag management.
|
||||
tag_manage_dialog = memnew(ConfirmationDialog);
|
||||
add_child(tag_manage_dialog);
|
||||
tag_manage_dialog->set_title(TTR("Manage Project Tags"));
|
||||
tag_manage_dialog->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_apply_project_tags));
|
||||
manage_tags_btn->connect("pressed", callable_mp(this, &ProjectManager::_manage_project_tags));
|
||||
|
||||
VBoxContainer *tag_vb = memnew(VBoxContainer);
|
||||
tag_manage_dialog->add_child(tag_vb);
|
||||
|
||||
Label *label = memnew(Label(TTR("Project Tags")));
|
||||
tag_vb->add_child(label);
|
||||
label->set_theme_type_variation("HeaderMedium");
|
||||
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
|
||||
label = memnew(Label(TTR("Click tag to remove it from the project.")));
|
||||
tag_vb->add_child(label);
|
||||
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
|
||||
project_tags = memnew(HFlowContainer);
|
||||
tag_vb->add_child(project_tags);
|
||||
project_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);
|
||||
|
||||
tag_vb->add_child(memnew(HSeparator));
|
||||
|
||||
label = memnew(Label(TTR("All Tags")));
|
||||
tag_vb->add_child(label);
|
||||
label->set_theme_type_variation("HeaderMedium");
|
||||
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
|
||||
label = memnew(Label(TTR("Click tag to add it to the project.")));
|
||||
tag_vb->add_child(label);
|
||||
label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
|
||||
all_tags = memnew(HFlowContainer);
|
||||
tag_vb->add_child(all_tags);
|
||||
all_tags->set_custom_minimum_size(Vector2(0, 100) * EDSCALE);
|
||||
|
||||
tag_edit_error = memnew(Label);
|
||||
tag_vb->add_child(tag_edit_error);
|
||||
tag_edit_error->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
|
||||
|
||||
create_tag_dialog = memnew(ConfirmationDialog);
|
||||
tag_manage_dialog->add_child(create_tag_dialog);
|
||||
create_tag_dialog->set_title(TTR("Create New Tag"));
|
||||
create_tag_dialog->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_create_new_tag));
|
||||
|
||||
tag_vb = memnew(VBoxContainer);
|
||||
create_tag_dialog->add_child(tag_vb);
|
||||
|
||||
Label *info = memnew(Label(TTR("Tags are capitalized automatically when displayed.")));
|
||||
tag_vb->add_child(info);
|
||||
|
||||
new_tag_name = memnew(LineEdit);
|
||||
tag_vb->add_child(new_tag_name);
|
||||
new_tag_name->connect("text_changed", callable_mp(this, &ProjectManager::_set_new_tag_name));
|
||||
new_tag_name->connect("text_submitted", callable_mp(this, &ProjectManager::_create_new_tag).unbind(1));
|
||||
create_tag_dialog->connect("about_to_popup", callable_mp(new_tag_name, &LineEdit::clear));
|
||||
create_tag_dialog->connect("about_to_popup", callable_mp((Control *)new_tag_name, &Control::grab_focus), CONNECT_DEFERRED);
|
||||
|
||||
tag_error = memnew(Label);
|
||||
tag_vb->add_child(tag_error);
|
||||
|
||||
create_tag_btn = memnew(Button);
|
||||
all_tags->add_child(create_tag_btn);
|
||||
create_tag_btn->connect("pressed", callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));
|
||||
}
|
||||
|
||||
_project_list->migrate_config();
|
||||
_load_recent_projects();
|
||||
|
||||
|
@ -2984,3 +3254,41 @@ ProjectManager::~ProjectManager() {
|
|||
EditorSettings::destroy();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectTag::_notification(int p_what) {
|
||||
if (display_close && p_what == NOTIFICATION_THEME_CHANGED) {
|
||||
button->set_icon(get_theme_icon(SNAME("close"), SNAME("TabBar")));
|
||||
}
|
||||
}
|
||||
|
||||
ProjectTag::ProjectTag(const String &p_text, bool p_display_close) {
|
||||
add_theme_constant_override(SNAME("separation"), 0);
|
||||
set_v_size_flags(SIZE_SHRINK_CENTER);
|
||||
tag_string = p_text;
|
||||
display_close = p_display_close;
|
||||
|
||||
Color tag_color = Color(1, 0, 0);
|
||||
tag_color.set_ok_hsl_s(0.8);
|
||||
tag_color.set_ok_hsl_h(float(p_text.hash() * 10001 % UINT32_MAX) / float(UINT32_MAX));
|
||||
set_self_modulate(tag_color);
|
||||
|
||||
ColorRect *cr = memnew(ColorRect);
|
||||
add_child(cr);
|
||||
cr->set_custom_minimum_size(Vector2(4, 0) * EDSCALE);
|
||||
cr->set_color(tag_color);
|
||||
|
||||
button = memnew(Button);
|
||||
add_child(button);
|
||||
button->set_text(p_text.capitalize());
|
||||
button->set_focus_mode(FOCUS_NONE);
|
||||
button->set_icon_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
|
||||
button->set_theme_type_variation(SNAME("ProjectTag"));
|
||||
}
|
||||
|
||||
void ProjectTag::connect_button_to(const Callable &p_callable) {
|
||||
button->connect(SNAME("pressed"), p_callable, CONNECT_DEFERRED);
|
||||
}
|
||||
|
||||
const String ProjectTag::get_tag() const {
|
||||
return tag_string;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@
|
|||
class CheckBox;
|
||||
class EditorAssetLibrary;
|
||||
class EditorFileDialog;
|
||||
class HFlowContainer;
|
||||
class PanelContainer;
|
||||
class ProjectList;
|
||||
|
||||
class ProjectDialog : public ConfirmationDialog {
|
||||
GDCLASS(ProjectDialog, ConfirmationDialog);
|
||||
|
@ -144,7 +146,8 @@ class ProjectListItemControl : public HBoxContainer {
|
|||
TextureRect *project_icon = nullptr;
|
||||
Label *project_title = nullptr;
|
||||
Label *project_path = nullptr;
|
||||
Label *project_unsupported_features = nullptr;
|
||||
TextureRect *project_unsupported_features = nullptr;
|
||||
HBoxContainer *tag_container = nullptr;
|
||||
|
||||
bool project_is_missing = false;
|
||||
bool icon_needs_reload = true;
|
||||
|
@ -161,6 +164,7 @@ protected:
|
|||
public:
|
||||
void set_project_title(const String &p_title);
|
||||
void set_project_path(const String &p_path);
|
||||
void set_tags(const PackedStringArray &p_tags, ProjectList *p_parent_list);
|
||||
void set_project_icon(const Ref<Texture2D> &p_icon);
|
||||
void set_unsupported_features(const PackedStringArray &p_features);
|
||||
|
||||
|
@ -184,12 +188,15 @@ public:
|
|||
EDIT_DATE,
|
||||
NAME,
|
||||
PATH,
|
||||
TAGS,
|
||||
};
|
||||
|
||||
// Can often be passed by copy
|
||||
struct Item {
|
||||
String project_name;
|
||||
String description;
|
||||
PackedStringArray tags;
|
||||
String tag_sort_string;
|
||||
String path;
|
||||
String icon;
|
||||
String main_scene;
|
||||
|
@ -206,6 +213,7 @@ public:
|
|||
|
||||
Item(const String &p_name,
|
||||
const String &p_description,
|
||||
const PackedStringArray &p_tags,
|
||||
const String &p_path,
|
||||
const String &p_icon,
|
||||
const String &p_main_scene,
|
||||
|
@ -217,6 +225,7 @@ public:
|
|||
int p_version) {
|
||||
project_name = p_name;
|
||||
description = p_description;
|
||||
tags = p_tags;
|
||||
path = p_path;
|
||||
icon = p_icon;
|
||||
main_scene = p_main_scene;
|
||||
|
@ -227,6 +236,10 @@ public:
|
|||
missing = p_missing;
|
||||
version = p_version;
|
||||
control = nullptr;
|
||||
|
||||
PackedStringArray sorted_tags = tags;
|
||||
sorted_tags.sort();
|
||||
tag_sort_string = String().join(sorted_tags);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const Item &l) const {
|
||||
|
@ -298,6 +311,7 @@ public:
|
|||
void erase_missing_projects();
|
||||
|
||||
void set_search_term(String p_search_term);
|
||||
void add_search_tag(const String &p_tag);
|
||||
void set_order_option(int p_option);
|
||||
|
||||
void update_dock_menu();
|
||||
|
@ -330,6 +344,7 @@ class ProjectManager : public Control {
|
|||
Button *open_btn = nullptr;
|
||||
Button *run_btn = nullptr;
|
||||
Button *rename_btn = nullptr;
|
||||
Button *manage_tags_btn = nullptr;
|
||||
Button *erase_btn = nullptr;
|
||||
Button *erase_missing_btn = nullptr;
|
||||
Button *about_btn = nullptr;
|
||||
|
@ -337,6 +352,8 @@ class ProjectManager : public Control {
|
|||
HBoxContainer *local_projects_hb = nullptr;
|
||||
EditorAssetLibrary *asset_library = nullptr;
|
||||
|
||||
Ref<StyleBox> tag_stylebox;
|
||||
|
||||
EditorFileDialog *scan_dir = nullptr;
|
||||
ConfirmationDialog *language_restart_ask = nullptr;
|
||||
|
||||
|
@ -365,6 +382,18 @@ class ProjectManager : public Control {
|
|||
OptionButton *language_btn = nullptr;
|
||||
LinkButton *version_btn = nullptr;
|
||||
|
||||
HashSet<String> tag_set;
|
||||
PackedStringArray current_project_tags;
|
||||
PackedStringArray forbidden_tag_characters{ "/", "\\", "-" };
|
||||
ConfirmationDialog *tag_manage_dialog = nullptr;
|
||||
HFlowContainer *project_tags = nullptr;
|
||||
HFlowContainer *all_tags = nullptr;
|
||||
Label *tag_edit_error = nullptr;
|
||||
Button *create_tag_btn = nullptr;
|
||||
ConfirmationDialog *create_tag_dialog = nullptr;
|
||||
LineEdit *new_tag_name = nullptr;
|
||||
Label *tag_error = nullptr;
|
||||
|
||||
void _open_asset_library();
|
||||
void _scan_projects();
|
||||
void _run_project();
|
||||
|
@ -386,6 +415,13 @@ class ProjectManager : public Control {
|
|||
void _restart_confirm();
|
||||
void _confirm_update_settings();
|
||||
|
||||
void _manage_project_tags();
|
||||
void _add_project_tag(const String &p_tag);
|
||||
void _delete_project_tag(const String &p_tag);
|
||||
void _apply_project_tags();
|
||||
void _set_new_tag_name(const String p_name);
|
||||
void _create_new_tag();
|
||||
|
||||
void _load_recent_projects();
|
||||
void _on_project_created(const String &dir);
|
||||
void _on_projects_updated();
|
||||
|
@ -414,8 +450,28 @@ protected:
|
|||
public:
|
||||
static ProjectManager *get_singleton() { return singleton; }
|
||||
|
||||
LineEdit *get_search_box();
|
||||
void add_new_tag(const String &p_tag);
|
||||
|
||||
ProjectManager();
|
||||
~ProjectManager();
|
||||
};
|
||||
|
||||
class ProjectTag : public HBoxContainer {
|
||||
GDCLASS(ProjectTag, HBoxContainer);
|
||||
|
||||
String tag_string;
|
||||
Button *button = nullptr;
|
||||
bool display_close = false;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
ProjectTag(const String &p_text, bool p_display_close = false);
|
||||
|
||||
void connect_button_to(const Callable &p_callable);
|
||||
const String get_tag() const;
|
||||
};
|
||||
|
||||
#endif // PROJECT_MANAGER_H
|
||||
|
|
Loading…
Reference in a new issue