summaryrefslogtreecommitdiff
path: root/editor/project_manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/project_manager.cpp')
-rw-r--r--editor/project_manager.cpp244
1 files changed, 155 insertions, 89 deletions
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index feb2cdd071..701676a7f8 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -195,7 +195,7 @@ private:
unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io);
if (!pkg) {
- set_message(TTR("Error opening package file, not in zip format."), MESSAGE_ERROR);
+ set_message(TTR("Error opening package file, not in ZIP format."), MESSAGE_ERROR);
memdelete(d);
get_ok()->set_disabled(true);
unzClose(pkg);
@@ -519,7 +519,7 @@ private:
unzFile pkg = unzOpen2(zip_path.utf8().get_data(), &io);
if (!pkg) {
- dialog_error->set_text(TTR("Error opening package file, not in zip format."));
+ dialog_error->set_text(TTR("Error opening package file, not in ZIP format."));
dialog_error->popup_centered_minsize();
return;
}
@@ -928,16 +928,36 @@ public:
TextureButton *favorite_button;
TextureRect *icon;
bool icon_needs_reload;
+ bool hover;
ProjectListItemControl() {
favorite_button = NULL;
icon = NULL;
icon_needs_reload = true;
+ hover = false;
}
void set_is_favorite(bool fav) {
favorite_button->set_modulate(fav ? Color(1, 1, 1, 1) : Color(1, 1, 1, 0.2));
}
+
+ void _notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_MOUSE_ENTER: {
+ hover = true;
+ update();
+ } break;
+ case NOTIFICATION_MOUSE_EXIT: {
+ hover = false;
+ update();
+ } break;
+ case NOTIFICATION_DRAW: {
+ if (hover) {
+ draw_style_box(get_stylebox("hover", "Tree"), Rect2(Point2(), get_size() - Size2(10, 0) * EDSCALE));
+ }
+ } break;
+ }
+ }
};
class ProjectList : public ScrollContainer {
@@ -946,10 +966,16 @@ public:
static const char *SIGNAL_SELECTION_CHANGED;
static const char *SIGNAL_PROJECT_ASK_OPEN;
+ enum MenuOptions {
+ GLOBAL_NEW_WINDOW,
+ GLOBAL_OPEN_PROJECT
+ };
+
// Can often be passed by copy
struct Item {
String project_key;
String project_name;
+ String description;
String path;
String icon;
String main_scene;
@@ -965,6 +991,7 @@ public:
Item(const String &p_project,
const String &p_name,
+ const String &p_description,
const String &p_path,
const String &p_icon,
const String &p_main_scene,
@@ -976,6 +1003,7 @@ public:
project_key = p_project;
project_name = p_name;
+ description = p_description;
path = p_path;
icon = p_icon;
main_scene = p_main_scene;
@@ -997,7 +1025,6 @@ public:
void load_projects();
void set_search_term(String p_search_term);
- void set_filter_option(ProjectListFilter::FilterOption p_option);
void set_order_option(ProjectListFilter::FilterOption p_option);
void sort_projects();
int get_project_count() const;
@@ -1030,7 +1057,6 @@ private:
static void load_project_data(const String &p_property_key, Item &p_item, bool p_favorite);
String _search_term;
- ProjectListFilter::FilterOption _filter_option;
ProjectListFilter::FilterOption _order_option;
Set<String> _selected_project_keys;
String _last_clicked; // Project key
@@ -1063,7 +1089,6 @@ struct ProjectListComparator {
};
ProjectList::ProjectList() {
- _filter_option = ProjectListFilter::FILTER_NAME;
_order_option = ProjectListFilter::FILTER_MODIFIED;
_scroll_children = memnew(VBoxContainer);
@@ -1109,7 +1134,7 @@ void ProjectList::load_project_icon(int p_index) {
Error err = img->load(item.icon.replace_first("res://", item.path + "/"));
if (err == OK) {
- img->resize(default_icon->get_width(), default_icon->get_height());
+ img->resize(default_icon->get_width(), default_icon->get_height(), Image::INTERPOLATE_LANCZOS);
Ref<ImageTexture> it = memnew(ImageTexture);
it->create_from_image(img);
icon = it;
@@ -1147,6 +1172,7 @@ void ProjectList::load_project_data(const String &p_property_key, Item &p_item,
grayed = true;
}
+ String description = cf->get_value("application", "config/description", "");
String icon = cf->get_value("application", "config/icon", "");
String main_scene = cf->get_value("application", "run/main_scene", "");
@@ -1168,7 +1194,7 @@ void ProjectList::load_project_data(const String &p_property_key, Item &p_item,
String project_key = p_property_key.get_slice("/", 1);
- p_item = Item(project_key, project_name, path, icon, main_scene, last_modified, p_favorite, grayed, missing, config_version);
+ p_item = Item(project_key, project_name, description, path, icon, main_scene, last_modified, p_favorite, grayed, missing, config_version);
}
void ProjectList::load_projects() {
@@ -1184,6 +1210,7 @@ void ProjectList::load_projects() {
_projects.clear();
_last_clicked = "";
_selected_project_keys.clear();
+ OS::get_singleton()->global_menu_clear("_dock");
// Load data
// TODO Would be nice to change how projects and favourites are stored... it complicates things a bit.
@@ -1221,6 +1248,9 @@ void ProjectList::load_projects() {
create_project_item_control(i);
}
+ OS::get_singleton()->global_menu_add_separator("_dock");
+ OS::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), GLOBAL_NEW_WINDOW, Variant());
+
sort_projects();
set_v_scroll(0);
@@ -1243,12 +1273,15 @@ void ProjectList::create_project_item_control(int p_index) {
hb->connect("draw", this, "_panel_draw", varray(hb));
hb->connect("gui_input", this, "_panel_input", varray(hb));
hb->add_constant_override("separation", 10 * EDSCALE);
+ hb->set_tooltip(item.description);
VBoxContainer *favorite_box = memnew(VBoxContainer);
favorite_box->set_name("FavoriteBox");
TextureButton *favorite = memnew(TextureButton);
favorite->set_name("FavoriteButton");
favorite->set_normal_texture(favorite_icon);
+ // This makes the project's "hover" style display correctly when hovering the favorite icon
+ favorite->set_mouse_filter(MOUSE_FILTER_PASS);
favorite->connect("pressed", this, "_favorite_pressed", varray(hb));
favorite_box->add_child(favorite);
favorite_box->set_alignment(BoxContainer::ALIGN_CENTER);
@@ -1258,19 +1291,22 @@ void ProjectList::create_project_item_control(int p_index) {
TextureRect *tf = memnew(TextureRect);
tf->set_texture(get_icon("DefaultProjectIcon", "EditorIcons"));
+ if (item.missing) {
+ tf->set_modulate(Color(1, 1, 1, 0.5));
+ }
hb->add_child(tf);
hb->icon = tf;
VBoxContainer *vb = memnew(VBoxContainer);
if (item.grayed)
- vb->set_modulate(Color(0.5, 0.5, 0.5));
+ vb->set_modulate(Color(1, 1, 1, 0.5));
vb->set_h_size_flags(SIZE_EXPAND_FILL);
hb->add_child(vb);
Control *ec = memnew(Control);
ec->set_custom_minimum_size(Size2(0, 1));
ec->set_mouse_filter(MOUSE_FILTER_PASS);
vb->add_child(ec);
- Label *title = memnew(Label(item.project_name));
+ Label *title = memnew(Label(!item.missing ? item.project_name : TTR("Missing Project")));
title->add_font_override("font", get_font("title", "EditorFonts"));
title->add_color_override("font_color", font_color);
title->set_clip_text(true);
@@ -1281,12 +1317,21 @@ void ProjectList::create_project_item_control(int p_index) {
vb->add_child(path_hb);
Button *show = memnew(Button);
- show->set_icon(get_icon("Load", "EditorIcons")); // Folder icon
+ // Display a folder icon if the project directory can be opened, or a "broken file" icon if it can't
+ show->set_icon(get_icon(!item.missing ? "Load" : "FileBroken", "EditorIcons"));
show->set_flat(true);
- show->set_modulate(Color(1, 1, 1, 0.5));
+ if (!item.grayed) {
+ // Don't make the icon less prominent if the parent is already grayed out
+ show->set_modulate(Color(1, 1, 1, 0.5));
+ }
path_hb->add_child(show);
- show->connect("pressed", this, "_show_project", varray(item.path));
- show->set_tooltip(TTR("Show in File Manager"));
+
+ if (!item.missing) {
+ show->connect("pressed", this, "_show_project", varray(item.path));
+ show->set_tooltip(TTR("Show in File Manager"));
+ } else {
+ show->set_tooltip(TTR("Error: Project is missing on the filesystem."));
+ }
Label *fpath = memnew(Label(item.path));
path_hb->add_child(fpath);
@@ -1296,6 +1341,7 @@ void ProjectList::create_project_item_control(int p_index) {
fpath->set_clip_text(true);
_scroll_children->add_child(hb);
+ OS::get_singleton()->global_menu_add_item("_dock", item.project_name + " ( " + item.path + " )", GLOBAL_OPEN_PROJECT, Variant(item.path.plus_file("project.godot")));
item.control = hb;
}
@@ -1303,16 +1349,10 @@ void ProjectList::set_search_term(String p_search_term) {
_search_term = p_search_term;
}
-void ProjectList::set_filter_option(ProjectListFilter::FilterOption p_option) {
- if (_filter_option != p_option) {
- _filter_option = p_option;
- }
-}
-
void ProjectList::set_order_option(ProjectListFilter::FilterOption p_option) {
if (_order_option != p_option) {
_order_option = p_option;
- EditorSettings::get_singleton()->set("project_manager/sorting_order", (int)_filter_option);
+ EditorSettings::get_singleton()->set("project_manager/sorting_order", (int)_order_option);
EditorSettings::get_singleton()->save();
}
}
@@ -1328,11 +1368,18 @@ void ProjectList::sort_projects() {
bool visible = true;
if (_search_term != "") {
- if (_filter_option == ProjectListFilter::FILTER_PATH) {
- visible = item.path.findn(_search_term) != -1;
- } else if (_filter_option == ProjectListFilter::FILTER_NAME) {
- visible = item.project_name.findn(_search_term) != -1;
+
+ String search_path;
+ if (_search_term.find("/") != -1) {
+ // Search path will match the whole path
+ search_path = item.path;
+ } else {
+ // Search path will only match the last path component to make searching more strict
+ search_path = item.path.get_file();
}
+
+ // When searching, display projects whose name or path contain the search term
+ visible = item.project_name.findn(_search_term) != -1 || search_path.findn(_search_term) != -1;
}
item.control->set_visible(visible);
@@ -1694,7 +1741,6 @@ void ProjectList::_bind_methods() {
ClassDB::bind_method("_panel_input", &ProjectList::_panel_input);
ClassDB::bind_method("_favorite_pressed", &ProjectList::_favorite_pressed);
ClassDB::bind_method("_show_project", &ProjectList::_show_project);
- //ClassDB::bind_method("_unhandled_input", &ProjectList::_unhandled_input);
ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN));
@@ -1730,7 +1776,7 @@ void ProjectManager::_dim_window() {
// Dim the project manager window while it's quitting to make it clearer that it's busy.
// No transition is applied, as the effect needs to be visible immediately
- float c = 0.4f;
+ float c = 0.5f;
Color dim_color = Color(c, c, c);
gui_base->set_modulate(dim_color);
}
@@ -1740,10 +1786,18 @@ void ProjectManager::_update_project_buttons() {
Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects();
bool empty_selection = selected_projects.empty();
+ bool is_missing_project_selected = false;
+ for (int i = 0; i < selected_projects.size(); ++i) {
+ if (selected_projects[i].missing) {
+ is_missing_project_selected = true;
+ break;
+ }
+ }
+
erase_btn->set_disabled(empty_selection);
- open_btn->set_disabled(empty_selection);
- rename_btn->set_disabled(empty_selection);
- run_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);
+ run_btn->set_disabled(empty_selection || is_missing_project_selected);
erase_missing_btn->set_visible(_project_list->is_any_project_missing());
}
@@ -1754,8 +1808,19 @@ void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) {
if (k.is_valid()) {
- if (!k->is_pressed())
+ if (!k->is_pressed()) {
return;
+ }
+
+ // Pressing Command + Q quits the Project Manager
+ // This is handled by the platform implementation on macOS,
+ // so only define the shortcut on other platforms
+#ifndef OSX_ENABLED
+ if (k->get_scancode_with_modifiers() == (KEY_MASK_CMD | KEY_Q)) {
+ _dim_window();
+ get_tree()->quit();
+ }
+#endif
if (tabs->get_current_tab() != 0)
return;
@@ -1794,7 +1859,7 @@ void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) {
break;
int index = _project_list->get_single_selected_index();
- if (index - 1 > 0) {
+ if (index > 0) {
_project_list->select_project(index - 1);
_project_list->ensure_project_visible(index - 1);
_update_project_buttons();
@@ -1834,7 +1899,6 @@ void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) {
void ProjectManager::_load_recent_projects() {
- _project_list->set_filter_option(project_filter->get_filter_option());
_project_list->set_order_option(project_order_filter->get_filter_option());
_project_list->set_search_term(project_filter->get_search_term());
_project_list->load_projects();
@@ -1867,6 +1931,29 @@ void ProjectManager::_confirm_update_settings() {
_open_selected_projects();
}
+void ProjectManager::_global_menu_action(const Variant &p_id, const Variant &p_meta) {
+
+ int id = (int)p_id;
+ if (id == ProjectList::GLOBAL_NEW_WINDOW) {
+ List<String> args;
+ String exec = OS::get_singleton()->get_executable_path();
+
+ OS::ProcessID pid = 0;
+ OS::get_singleton()->execute(exec, args, false, &pid);
+ } else if (id == ProjectList::GLOBAL_OPEN_PROJECT) {
+ String conf = (String)p_meta;
+
+ if (conf != String()) {
+ List<String> args;
+ args.push_back(conf);
+ String exec = OS::get_singleton()->get_executable_path();
+
+ OS::ProcessID pid = 0;
+ OS::get_singleton()->execute(exec, args, false, &pid);
+ }
+ }
+}
+
void ProjectManager::_open_selected_projects() {
const Set<String> &selected_list = _project_list->get_selected_project_keys();
@@ -1921,6 +2008,9 @@ void ProjectManager::_open_selected_projects_ask() {
}
ProjectList::Item project = _project_list->get_selected_projects()[0];
+ if (project.missing) {
+ return;
+ }
// Update the project settings or don't open
String conf = project.path.plus_file("project.godot");
@@ -2075,10 +2165,12 @@ void ProjectManager::_rename_project() {
void ProjectManager::_erase_project_confirm() {
_project_list->erase_selected_projects();
+ _update_project_buttons();
}
void ProjectManager::_erase_missing_projects_confirm() {
_project_list->erase_missing_projects();
+ _update_project_buttons();
}
void ProjectManager::_erase_project() {
@@ -2101,7 +2193,7 @@ void ProjectManager::_erase_project() {
void ProjectManager::_erase_missing_projects() {
- erase_missing_ask->set_text(TTR("Remove all missing projects from the list? (Folders contents will not be modified)"));
+ erase_missing_ask->set_text(TTR("Remove all missing projects from the list?\nThe project folders' contents won't be modified."));
erase_missing_ask->popup_centered_minsize();
}
@@ -2196,7 +2288,6 @@ void ProjectManager::_on_order_option_changed() {
}
void ProjectManager::_on_filter_option_changed() {
- _project_list->set_filter_option(project_filter->get_filter_option());
_project_list->set_search_term(project_filter->get_search_term());
_project_list->sort_projects();
}
@@ -2205,6 +2296,7 @@ void ProjectManager::_bind_methods() {
ClassDB::bind_method("_open_selected_projects_ask", &ProjectManager::_open_selected_projects_ask);
ClassDB::bind_method("_open_selected_projects", &ProjectManager::_open_selected_projects);
+ ClassDB::bind_method(D_METHOD("_global_menu_action"), &ProjectManager::_global_menu_action, DEFVAL(Variant()));
ClassDB::bind_method("_run_project", &ProjectManager::_run_project);
ClassDB::bind_method("_run_project_confirm", &ProjectManager::_run_project_confirm);
ClassDB::bind_method("_scan_projects", &ProjectManager::_scan_projects);
@@ -2268,6 +2360,9 @@ ProjectManager::ProjectManager() {
} break;
}
+ // Define a minimum window size to prevent UI elements from overlapping or being cut off
+ OS::get_singleton()->set_min_window_size(Size2(750, 420) * EDSCALE);
+
#ifndef OSX_ENABLED
// The macOS platform implementation uses its own hiDPI window resizing code
// TODO: Resize windows on hiDPI displays on Windows and Linux and remove the line below
@@ -2293,26 +2388,11 @@ ProjectManager::ProjectManager() {
VBoxContainer *vb = memnew(VBoxContainer);
panel->add_child(vb);
vb->set_anchors_and_margins_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 8 * EDSCALE);
- vb->add_constant_override("separation", 8 * EDSCALE);
String cp;
cp += 0xA9;
OS::get_singleton()->set_window_title(VERSION_NAME + String(" - ") + TTR("Project Manager") + " - " + cp + " 2007-2019 Juan Linietsky, Ariel Manzur & Godot Contributors");
- HBoxContainer *top_hb = memnew(HBoxContainer);
- vb->add_child(top_hb);
- Label *l = memnew(Label);
- l->set_text(VERSION_NAME + String(" - ") + TTR("Project Manager"));
- top_hb->add_child(l);
- top_hb->add_spacer();
- l = memnew(Label);
- String hash = String(VERSION_HASH);
- if (hash.length() != 0)
- hash = "." + hash.left(9);
- l->set_text("v" VERSION_FULL_BUILD "" + hash);
- l->set_align(Label::ALIGN_CENTER);
- top_hb->add_child(l);
-
Control *center_box = memnew(Control);
center_box->set_v_size_flags(SIZE_EXPAND_FILL);
vb->add_child(center_box);
@@ -2320,11 +2400,12 @@ ProjectManager::ProjectManager() {
tabs = memnew(TabContainer);
center_box->add_child(tabs);
tabs->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ tabs->set_tab_align(TabContainer::ALIGN_LEFT);
HBoxContainer *tree_hb = memnew(HBoxContainer);
projects_hb = tree_hb;
- projects_hb->set_name(TTR("Project List"));
+ projects_hb->set_name(TTR("Projects"));
tabs->add_child(tree_hb);
@@ -2341,6 +2422,7 @@ ProjectManager::ProjectManager() {
sort_filter_titles.push_back("Path");
sort_filter_titles.push_back("Last Modified");
project_order_filter = memnew(ProjectListFilter);
+ project_order_filter->add_filter_option();
project_order_filter->_setup_filters(sort_filter_titles);
project_order_filter->set_filter_size(150);
sort_filters->add_child(project_order_filter);
@@ -2351,21 +2433,12 @@ ProjectManager::ProjectManager() {
project_order_filter->set_filter_option((ProjectListFilter::FilterOption)projects_sorting_order);
sort_filters->add_spacer(true);
- Label *search_label = memnew(Label);
- search_label->set_text(TTR("Search:"));
- sort_filters->add_child(search_label);
-
- HBoxContainer *search_filters = memnew(HBoxContainer);
- Vector<String> vec2;
- vec2.push_back("Name");
- vec2.push_back("Path");
+
project_filter = memnew(ProjectListFilter);
- project_filter->_setup_filters(vec2);
project_filter->add_search_box();
- search_filters->add_child(project_filter);
project_filter->connect("filter_changed", this, "_on_filter_option_changed");
project_filter->set_custom_minimum_size(Size2(280, 10) * EDSCALE);
- sort_filters->add_child(search_filters);
+ sort_filters->add_child(project_filter);
search_tree_vb->add_child(sort_filters);
@@ -2455,6 +2528,17 @@ ProjectManager::ProjectManager() {
settings_hb->set_alignment(BoxContainer::ALIGN_END);
settings_hb->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN);
+ Label *version_label = memnew(Label);
+ String hash = String(VERSION_HASH);
+ if (hash.length() != 0) {
+ hash = "." + hash.left(9);
+ }
+ version_label->set_text("v" VERSION_FULL_BUILD "" + hash);
+ // Fade out the version label to be less prominent, but still readable
+ version_label->set_self_modulate(Color(1, 1, 1, 0.6));
+ version_label->set_align(Label::ALIGN_CENTER);
+ settings_hb->add_child(version_label);
+
language_btn = memnew(OptionButton);
language_btn->set_flat(true);
language_btn->set_focus_mode(Control::FOCUS_NONE);
@@ -2487,27 +2571,6 @@ ProjectManager::ProjectManager() {
center_box->add_child(settings_hb);
settings_hb->set_anchors_and_margins_preset(Control::PRESET_TOP_RIGHT);
- CenterContainer *cc = memnew(CenterContainer);
- Button *cancel = memnew(Button);
- cancel->set_text(TTR("Exit"));
- cancel->set_custom_minimum_size(Size2(100, 1) * EDSCALE);
-
-#ifndef OSX_ENABLED
- // Pressing Command + Q quits the Project Manager
- // This is handled by the platform implementation on macOS,
- // so only define the shortcut on other platforms
- InputEventKey *quit_key = memnew(InputEventKey);
- quit_key->set_command(true);
- quit_key->set_scancode(KEY_Q);
- ShortCut *quit_shortcut = memnew(ShortCut);
- quit_shortcut->set_shortcut(quit_key);
- cancel->set_shortcut(quit_shortcut);
-#endif
-
- cc->add_child(cancel);
- cancel->connect("pressed", this, "_exit_dialog");
- vb->add_child(cc);
-
//////////////////////////////////////////////////////////////
language_restart_ask = memnew(ConfirmationDialog);
@@ -2559,6 +2622,7 @@ ProjectManager::ProjectManager() {
}
SceneTree::get_singleton()->connect("files_dropped", this, "_files_dropped");
+ SceneTree::get_singleton()->connect("global_menu_action", this, "_global_menu_action");
run_error_diag = memnew(AcceptDialog);
gui_base->add_child(run_error_diag);
@@ -2628,11 +2692,20 @@ void ProjectListFilter::_bind_methods() {
ADD_SIGNAL(MethodInfo("filter_changed"));
}
+void ProjectListFilter::add_filter_option() {
+ filter_option = memnew(OptionButton);
+ filter_option->set_clip_text(true);
+ filter_option->connect("item_selected", this, "_filter_option_selected");
+ add_child(filter_option);
+}
+
void ProjectListFilter::add_search_box() {
search_box = memnew(LineEdit);
+ search_box->set_placeholder(TTR("Search"));
search_box->connect("text_changed", this, "_search_text_changed");
search_box->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(search_box);
+
has_search_box = true;
}
@@ -2643,13 +2716,6 @@ void ProjectListFilter::set_filter_size(int h_size) {
ProjectListFilter::ProjectListFilter() {
_current_filter = FILTER_NAME;
-
- filter_option = memnew(OptionButton);
- set_filter_size(80);
- filter_option->set_clip_text(true);
- filter_option->connect("item_selected", this, "_filter_option_selected");
- add_child(filter_option);
-
has_search_box = false;
}