1
0
Fork 0

Reorganize ProjectManager code to be more idiomatic

This commit is contained in:
Yuri Sizov 2023-03-10 13:57:32 +01:00
parent ac2e82463c
commit 10420f91ba
2 changed files with 1071 additions and 1021 deletions

View File

@ -47,6 +47,7 @@
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_themes.h" #include "editor/editor_themes.h"
#include "editor/editor_vcs_interface.h" #include "editor/editor_vcs_interface.h"
#include "editor/plugins/asset_library_editor_plugin.h"
#include "main/main.h" #include "main/main.h"
#include "scene/gui/center_container.h" #include "scene/gui/center_container.h"
#include "scene/gui/check_box.h" #include "scene/gui/check_box.h"
@ -62,58 +63,9 @@
constexpr int GODOT4_CONFIG_VERSION = 5; constexpr int GODOT4_CONFIG_VERSION = 5;
class ProjectDialog : public ConfirmationDialog { /// Project Dialog.
GDCLASS(ProjectDialog, ConfirmationDialog);
public: void ProjectDialog::_set_message(const String &p_msg, MessageType p_type, InputType input_type) {
bool is_folder_empty = true;
enum Mode {
MODE_NEW,
MODE_IMPORT,
MODE_INSTALL,
MODE_RENAME,
};
private:
enum MessageType {
MESSAGE_ERROR,
MESSAGE_WARNING,
MESSAGE_SUCCESS,
};
enum InputType {
PROJECT_PATH,
INSTALL_PATH,
};
Mode mode;
Button *browse;
Button *install_browse;
Button *create_dir;
Container *name_container;
Container *path_container;
Container *install_path_container;
Container *renderer_container;
Label *renderer_info;
HBoxContainer *default_files_container;
Ref<ButtonGroup> renderer_button_group;
Label *msg;
LineEdit *project_path;
LineEdit *project_name;
LineEdit *install_path;
TextureRect *status_rect;
TextureRect *install_status_rect;
EditorFileDialog *fdialog;
EditorFileDialog *fdialog_install;
OptionButton *vcs_metadata_selection;
String zip_path;
String zip_title;
AcceptDialog *dialog_error;
String fav_dir;
String created_folder_path;
void set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS, InputType input_type = PROJECT_PATH) {
msg->set_text(p_msg); msg->set_text(p_msg);
Ref<Texture2D> current_path_icon = status_rect->get_texture(); Ref<Texture2D> current_path_icon = status_rect->get_texture();
Ref<Texture2D> current_install_icon = install_status_rect->get_texture(); Ref<Texture2D> current_install_icon = install_status_rect->get_texture();
@ -150,9 +102,9 @@ private:
if (window_size.x < contents_min_size.x || window_size.y < contents_min_size.y) { if (window_size.x < contents_min_size.x || window_size.y < contents_min_size.y) {
set_size(window_size.max(contents_min_size)); set_size(window_size.max(contents_min_size));
} }
} }
String _test_path() { String ProjectDialog::_test_path() {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String valid_path, valid_install_path; String valid_path, valid_install_path;
if (d->change_dir(project_path->get_text()) == OK) { if (d->change_dir(project_path->get_text()) == OK) {
@ -170,7 +122,7 @@ private:
} }
if (valid_path.is_empty()) { if (valid_path.is_empty()) {
set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR); _set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return ""; return "";
} }
@ -183,7 +135,7 @@ private:
} }
if (valid_install_path.is_empty()) { if (valid_install_path.is_empty()) {
set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR, INSTALL_PATH); _set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR, INSTALL_PATH);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return ""; return "";
} }
@ -197,7 +149,7 @@ private:
unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io); unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io);
if (!pkg) { if (!pkg) {
set_message(TTR("Error opening package file (it's not in ZIP format)."), MESSAGE_ERROR); _set_message(TTR("Error opening package file (it's not in ZIP format)."), MESSAGE_ERROR);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
unzClose(pkg); unzClose(pkg);
return ""; return "";
@ -220,7 +172,7 @@ private:
} }
if (ret == UNZ_END_OF_LIST_OF_FILE) { if (ret == UNZ_END_OF_LIST_OF_FILE) {
set_message(TTR("Invalid \".zip\" project file; it doesn't contain a \"project.godot\" file."), MESSAGE_ERROR); _set_message(TTR("Invalid \".zip\" project file; it doesn't contain a \"project.godot\" file."), MESSAGE_ERROR);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
unzClose(pkg); unzClose(pkg);
return ""; return "";
@ -246,20 +198,20 @@ private:
d->list_dir_end(); d->list_dir_end();
if (!is_folder_empty) { if (!is_folder_empty) {
set_message(TTR("Please choose an empty folder."), MESSAGE_WARNING, INSTALL_PATH); _set_message(TTR("Please choose an empty folder."), MESSAGE_WARNING, INSTALL_PATH);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return ""; return "";
} }
} else { } else {
set_message(TTR("Please choose a \"project.godot\" or \".zip\" file."), MESSAGE_ERROR); _set_message(TTR("Please choose a \"project.godot\" or \".zip\" file."), MESSAGE_ERROR);
install_path_container->hide(); install_path_container->hide();
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return ""; return "";
} }
} else if (valid_path.ends_with("zip")) { } else if (valid_path.ends_with("zip")) {
set_message(TTR("This directory already contains a Godot project."), MESSAGE_ERROR, INSTALL_PATH); _set_message(TTR("This directory already contains a Godot project."), MESSAGE_ERROR, INSTALL_PATH);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return ""; return "";
} }
@ -284,24 +236,24 @@ private:
if (!is_folder_empty) { if (!is_folder_empty) {
if (valid_path == OS::get_singleton()->get_environment("HOME") || valid_path == OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS) || valid_path == OS::get_singleton()->get_executable_path().get_base_dir()) { if (valid_path == OS::get_singleton()->get_environment("HOME") || valid_path == OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS) || valid_path == OS::get_singleton()->get_executable_path().get_base_dir()) {
set_message(TTR("You cannot save a project in the selected path. Please make a new folder or choose a new path."), MESSAGE_ERROR); _set_message(TTR("You cannot save a project in the selected path. Please make a new folder or choose a new path."), MESSAGE_ERROR);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return ""; return "";
} }
set_message(TTR("The selected path is not empty. Choosing an empty folder is highly recommended."), MESSAGE_WARNING); _set_message(TTR("The selected path is not empty. Choosing an empty folder is highly recommended."), MESSAGE_WARNING);
get_ok_button()->set_disabled(false); get_ok_button()->set_disabled(false);
return valid_path; return valid_path;
} }
} }
set_message(""); _set_message("");
set_message("", MESSAGE_SUCCESS, INSTALL_PATH); _set_message("", MESSAGE_SUCCESS, INSTALL_PATH);
get_ok_button()->set_disabled(false); get_ok_button()->set_disabled(false);
return valid_path; return valid_path;
} }
void _path_text_changed(const String &p_path) { void ProjectDialog::_path_text_changed(const String &p_path) {
String sp = _test_path(); String sp = _test_path();
if (!sp.is_empty()) { if (!sp.is_empty()) {
// If the project name is empty or default, infer the project name from the selected folder name // If the project name is empty or default, infer the project name from the selected folder name
@ -324,9 +276,9 @@ private:
if (!created_folder_path.is_empty() && created_folder_path != p_path) { if (!created_folder_path.is_empty() && created_folder_path != p_path) {
_remove_created_folder(); _remove_created_folder();
} }
} }
void _file_selected(const String &p_path) { void ProjectDialog::_file_selected(const String &p_path) {
String p = p_path; String p = p_path;
if (mode == MODE_IMPORT) { if (mode == MODE_IMPORT) {
if (p.ends_with("project.godot")) { if (p.ends_with("project.godot")) {
@ -338,7 +290,7 @@ private:
install_path_container->show(); install_path_container->show();
get_ok_button()->set_disabled(false); get_ok_button()->set_disabled(false);
} else { } else {
set_message(TTR("Please choose a \"project.godot\" or \".zip\" file."), MESSAGE_ERROR); _set_message(TTR("Please choose a \"project.godot\" or \".zip\" file."), MESSAGE_ERROR);
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
return; return;
} }
@ -352,23 +304,23 @@ private:
} else { } else {
get_ok_button()->call_deferred(SNAME("grab_focus")); get_ok_button()->call_deferred(SNAME("grab_focus"));
} }
} }
void _path_selected(const String &p_path) { void ProjectDialog::_path_selected(const String &p_path) {
String sp = p_path.simplify_path(); String sp = p_path.simplify_path();
project_path->set_text(sp); project_path->set_text(sp);
_path_text_changed(sp); _path_text_changed(sp);
get_ok_button()->call_deferred(SNAME("grab_focus")); get_ok_button()->call_deferred(SNAME("grab_focus"));
} }
void _install_path_selected(const String &p_path) { void ProjectDialog::_install_path_selected(const String &p_path) {
String sp = p_path.simplify_path(); String sp = p_path.simplify_path();
install_path->set_text(sp); install_path->set_text(sp);
_path_text_changed(sp); _path_text_changed(sp);
get_ok_button()->call_deferred(SNAME("grab_focus")); get_ok_button()->call_deferred(SNAME("grab_focus"));
} }
void _browse_path() { void ProjectDialog::_browse_path() {
fdialog->set_current_dir(project_path->get_text()); fdialog->set_current_dir(project_path->get_text());
if (mode == MODE_IMPORT) { if (mode == MODE_IMPORT) {
@ -380,18 +332,18 @@ private:
fdialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); fdialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
} }
fdialog->popup_file_dialog(); fdialog->popup_file_dialog();
} }
void _browse_install_path() { void ProjectDialog::_browse_install_path() {
fdialog_install->set_current_dir(install_path->get_text()); fdialog_install->set_current_dir(install_path->get_text());
fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); fdialog_install->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
fdialog_install->popup_file_dialog(); fdialog_install->popup_file_dialog();
} }
void _create_folder() { void ProjectDialog::_create_folder() {
const String project_name_no_edges = project_name->get_text().strip_edges(); const String project_name_no_edges = project_name->get_text().strip_edges();
if (project_name_no_edges.is_empty() || !created_folder_path.is_empty() || project_name_no_edges.ends_with(".")) { if (project_name_no_edges.is_empty() || !created_folder_path.is_empty() || project_name_no_edges.ends_with(".")) {
set_message(TTR("Invalid project name."), MESSAGE_WARNING); _set_message(TTR("Invalid project name."), MESSAGE_WARNING);
return; return;
} }
@ -414,9 +366,9 @@ private:
dialog_error->popup_centered(); dialog_error->popup_centered();
} }
} }
} }
void _text_changed(const String &p_text) { void ProjectDialog::_text_changed(const String &p_text) {
if (mode != MODE_NEW) { if (mode != MODE_NEW) {
return; return;
} }
@ -424,16 +376,16 @@ private:
_test_path(); _test_path();
if (p_text.strip_edges().is_empty()) { if (p_text.strip_edges().is_empty()) {
set_message(TTR("It would be a good idea to name your project."), MESSAGE_ERROR); _set_message(TTR("It would be a good idea to name your project."), MESSAGE_ERROR);
}
} }
}
void _nonempty_confirmation_ok_pressed() { void ProjectDialog::_nonempty_confirmation_ok_pressed() {
is_folder_empty = true; is_folder_empty = true;
ok_pressed(); ok_pressed();
} }
void _renderer_selected() { void ProjectDialog::_renderer_selected() {
String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method")); String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method"));
if (renderer_type == "forward_plus") { if (renderer_type == "forward_plus") {
@ -460,15 +412,25 @@ private:
} else { } else {
WARN_PRINT("Unknown renderer type. Please report this as a bug on GitHub."); WARN_PRINT("Unknown renderer type. Please report this as a bug on GitHub.");
} }
} }
void ok_pressed() override { void ProjectDialog::_remove_created_folder() {
if (!created_folder_path.is_empty()) {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
d->remove(created_folder_path);
create_dir->set_disabled(false);
created_folder_path = "";
}
}
void ProjectDialog::ok_pressed() {
String dir = project_path->get_text(); String dir = project_path->get_text();
if (mode == MODE_RENAME) { if (mode == MODE_RENAME) {
String dir2 = _test_path(); String dir2 = _test_path();
if (dir2.is_empty()) { if (dir2.is_empty()) {
set_message(TTR("Invalid project path (changed anything?)."), MESSAGE_ERROR); _set_message(TTR("Invalid project path (changed anything?)."), MESSAGE_ERROR);
return; return;
} }
@ -477,12 +439,12 @@ private:
String project_godot = dir2.path_join("project.godot"); String project_godot = dir2.path_join("project.godot");
Error err = cfg.load(project_godot); Error err = cfg.load(project_godot);
if (err != OK) { if (err != OK) {
set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR); _set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR);
} else { } else {
cfg.set_value("application", "config/name", project_name->get_text().strip_edges()); cfg.set_value("application", "config/name", project_name->get_text().strip_edges());
err = cfg.save(project_godot); err = cfg.save(project_godot);
if (err != OK) { if (err != OK) {
set_message(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err), MESSAGE_ERROR); _set_message(vformat(TTR("Couldn't save project at '%s' (error %d)."), project_godot, err), MESSAGE_ERROR);
} }
} }
@ -542,7 +504,7 @@ private:
initial_settings["application/config/icon"] = "res://icon.svg"; initial_settings["application/config/icon"] = "res://icon.svg";
if (ProjectSettings::get_singleton()->save_custom(dir.path_join("project.godot"), initial_settings, Vector<String>(), false) != OK) { if (ProjectSettings::get_singleton()->save_custom(dir.path_join("project.godot"), initial_settings, Vector<String>(), false) != OK) {
set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR); _set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR);
} else { } else {
// Store default project icon in SVG format. // Store default project icon in SVG format.
Error err; Error err;
@ -550,7 +512,7 @@ private:
fa_icon->store_string(get_default_project_icon()); fa_icon->store_string(get_default_project_icon());
if (err != OK) { if (err != OK) {
set_message(TTR("Couldn't create icon.svg in project path."), MESSAGE_ERROR); _set_message(TTR("Couldn't create icon.svg in project path."), MESSAGE_ERROR);
} }
EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), dir); EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(vcs_metadata_selection->get_selected()), dir);
@ -663,19 +625,9 @@ private:
hide(); hide();
emit_signal(SNAME("project_created"), dir); emit_signal(SNAME("project_created"), dir);
} }
} }
void _remove_created_folder() { void ProjectDialog::cancel_pressed() {
if (!created_folder_path.is_empty()) {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
d->remove(created_folder_path);
create_dir->set_disabled(false);
created_folder_path = "";
}
}
void cancel_pressed() override {
_remove_created_folder(); _remove_created_folder();
project_path->clear(); project_path->clear();
@ -690,39 +642,25 @@ private:
if (install_status_rect->get_texture() == msg->get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons"))) { if (install_status_rect->get_texture() == msg->get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons"))) {
msg->show(); msg->show();
} }
} }
void _notification(int p_what) { void ProjectDialog::set_zip_path(const String &p_path) {
switch (p_what) {
case NOTIFICATION_WM_CLOSE_REQUEST: {
_remove_created_folder();
} break;
}
}
protected:
static void _bind_methods() {
ADD_SIGNAL(MethodInfo("project_created"));
ADD_SIGNAL(MethodInfo("projects_updated"));
}
public:
void set_zip_path(const String &p_path) {
zip_path = p_path; zip_path = p_path;
} }
void set_zip_title(const String &p_title) {
void ProjectDialog::set_zip_title(const String &p_title) {
zip_title = p_title; zip_title = p_title;
} }
void set_mode(Mode p_mode) { void ProjectDialog::set_mode(Mode p_mode) {
mode = p_mode; mode = p_mode;
} }
void set_project_path(const String &p_path) { void ProjectDialog::set_project_path(const String &p_path) {
project_path->set_text(p_path); project_path->set_text(p_path);
} }
void show_dialog() { void ProjectDialog::show_dialog() {
if (mode == MODE_RENAME) { if (mode == MODE_RENAME) {
project_path->set_editable(false); project_path->set_editable(false);
browse->hide(); browse->hide();
@ -744,7 +682,7 @@ public:
String project_godot = project_path->get_text().path_join("project.godot"); String project_godot = project_path->get_text().path_join("project.godot");
Error err = cfg.load(project_godot); Error err = cfg.load(project_godot);
if (err != OK) { if (err != OK) {
set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR); _set_message(vformat(TTR("Couldn't load project at '%s' (error %d). It may be missing or corrupted."), project_godot, err), MESSAGE_ERROR);
status_rect->show(); status_rect->show();
msg->show(); msg->show();
get_ok_button()->set_disabled(true); get_ok_button()->set_disabled(true);
@ -816,9 +754,22 @@ public:
} }
popup_centered(Size2(500, 0) * EDSCALE); popup_centered(Size2(500, 0) * EDSCALE);
} }
ProjectDialog() { void ProjectDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_WM_CLOSE_REQUEST: {
_remove_created_folder();
} break;
}
}
void ProjectDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("project_created"));
ADD_SIGNAL(MethodInfo("projects_updated"));
}
ProjectDialog::ProjectDialog() {
VBoxContainer *vb = memnew(VBoxContainer); VBoxContainer *vb = memnew(VBoxContainer);
add_child(vb); add_child(vb);
@ -1005,35 +956,14 @@ public:
fdialog_install->connect("file_selected", callable_mp(this, &ProjectDialog::_install_path_selected)); fdialog_install->connect("file_selected", callable_mp(this, &ProjectDialog::_install_path_selected));
set_hide_on_ok(false); set_hide_on_ok(false);
mode = MODE_NEW;
dialog_error = memnew(AcceptDialog); dialog_error = memnew(AcceptDialog);
add_child(dialog_error); add_child(dialog_error);
} }
};
class ProjectListItemControl : public HBoxContainer { /// Project List and friends.
GDCLASS(ProjectListItemControl, HBoxContainer)
public:
TextureButton *favorite_button;
TextureRect *icon;
bool icon_needs_reload;
bool hover;
ProjectListItemControl() { void ProjectListItemControl::_notification(int p_what) {
favorite_button = nullptr;
icon = nullptr;
icon_needs_reload = true;
hover = false;
set_focus_mode(FocusMode::FOCUS_ALL);
}
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) { switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: { case NOTIFICATION_MOUSE_ENTER: {
hover = true; hover = true;
@ -1051,129 +981,18 @@ public:
} }
} break; } break;
} }
} }
};
class ProjectList : public ScrollContainer { void ProjectListItemControl::set_is_favorite(bool fav) {
GDCLASS(ProjectList, ScrollContainer) favorite_button->set_modulate(fav ? Color(1, 1, 1, 1) : Color(1, 1, 1, 0.2));
public: }
static const char *SIGNAL_SELECTION_CHANGED;
static const char *SIGNAL_PROJECT_ASK_OPEN;
enum MenuOptions { ProjectListItemControl::ProjectListItemControl() {
GLOBAL_NEW_WINDOW, set_focus_mode(FocusMode::FOCUS_ALL);
GLOBAL_OPEN_PROJECT }
};
// Can often be passed by copy
struct Item {
String project_name;
String description;
String path;
String icon;
String main_scene;
PackedStringArray unsupported_features;
uint64_t last_edited = 0;
bool favorite = false;
bool grayed = false;
bool missing = false;
int version = 0;
ProjectListItemControl *control = nullptr;
Item() {}
Item(const String &p_name,
const String &p_description,
const String &p_path,
const String &p_icon,
const String &p_main_scene,
const PackedStringArray &p_unsupported_features,
uint64_t p_last_edited,
bool p_favorite,
bool p_grayed,
bool p_missing,
int p_version) {
project_name = p_name;
description = p_description;
path = p_path;
icon = p_icon;
main_scene = p_main_scene;
unsupported_features = p_unsupported_features;
last_edited = p_last_edited;
favorite = p_favorite;
grayed = p_grayed;
missing = p_missing;
version = p_version;
control = nullptr;
}
_FORCE_INLINE_ bool operator==(const Item &l) const {
return path == l.path;
}
};
bool project_opening_initiated;
ProjectList();
~ProjectList();
void _global_menu_new_window(const Variant &p_tag);
void _global_menu_open_project(const Variant &p_tag);
void update_dock_menu();
void migrate_config();
void load_projects();
void set_search_term(String p_search_term);
void set_order_option(int p_option);
void sort_projects();
int get_project_count() const;
void select_project(int p_index);
void select_first_visible_project();
void erase_selected_projects(bool p_delete_project_contents);
Vector<Item> get_selected_projects() const;
const HashSet<String> &get_selected_project_keys() const;
void ensure_project_visible(int p_index);
int get_single_selected_index() const;
bool is_any_project_missing() const;
void erase_missing_projects();
int refresh_project(const String &dir_path);
void add_project(const String &dir_path, bool favorite);
void save_config();
void set_project_version(const String &p_project_path, int version);
private:
static void _bind_methods();
void _notification(int p_what);
void _panel_draw(Node *p_hb);
void _panel_input(const Ref<InputEvent> &p_ev, Node *p_hb);
void _favorite_pressed(Node *p_hb);
void _show_project(const String &p_path);
void select_range(int p_begin, int p_end);
void toggle_select(int p_index);
void create_project_item_control(int p_index);
void remove_project(int p_index, bool p_update_settings);
void update_icons_async();
void load_project_icon(int p_index);
static Item load_project_data(const String &p_property_key, bool p_favorite);
String _search_term;
FilterOption _order_option;
HashSet<String> _selected_project_paths;
String _last_clicked; // Project key
VBoxContainer *_scroll_children;
int _icon_load_index;
Vector<Item> _projects;
ConfigFile _config;
String _config_path;
};
struct ProjectListComparator { struct ProjectListComparator {
FilterOption order_option = FilterOption::EDIT_DATE; ProjectList::FilterOption order_option = ProjectList::FilterOption::EDIT_DATE;
// operator< // operator<
_FORCE_INLINE_ bool operator()(const ProjectList::Item &a, const ProjectList::Item &b) const { _FORCE_INLINE_ bool operator()(const ProjectList::Item &a, const ProjectList::Item &b) const {
@ -1184,9 +1003,9 @@ struct ProjectListComparator {
return false; return false;
} }
switch (order_option) { switch (order_option) {
case PATH: case ProjectList::PATH:
return a.path < b.path; return a.path < b.path;
case EDIT_DATE: case ProjectList::EDIT_DATE:
return a.last_edited > b.last_edited; return a.last_edited > b.last_edited;
default: default:
return a.project_name < b.project_name; return a.project_name < b.project_name;
@ -1194,24 +1013,8 @@ struct ProjectListComparator {
} }
}; };
ProjectList::ProjectList() { const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
_order_option = FilterOption::EDIT_DATE; const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
_scroll_children = memnew(VBoxContainer);
_scroll_children->set_h_size_flags(Control::SIZE_EXPAND_FILL);
add_child(_scroll_children);
_icon_load_index = 0;
project_opening_initiated = false;
_config_path = EditorPaths::get_singleton()->get_data_dir().path_join("projects.cfg");
}
ProjectList::~ProjectList() {
}
void ProjectList::update_icons_async() {
_icon_load_index = 0;
set_process(true);
}
void ProjectList::_notification(int p_what) { void ProjectList::_notification(int p_what) {
switch (p_what) { switch (p_what) {
@ -1231,6 +1034,11 @@ void ProjectList::_notification(int p_what) {
} }
} }
void ProjectList::update_icons_async() {
_icon_load_index = 0;
set_process(true);
}
void ProjectList::load_project_icon(int p_index) { void ProjectList::load_project_icon(int p_index) {
Item &item = _projects.write[p_index]; Item &item = _projects.write[p_index];
@ -1259,7 +1067,8 @@ void ProjectList::load_project_icon(int p_index) {
item.control->icon_needs_reload = false; item.control->icon_needs_reload = false;
} }
// Load project data from p_property_key and return it in a ProjectList::Item. p_favorite is passed directly into the Item. // Load project data from p_property_key and return it in a ProjectList::Item.
// p_favorite is passed directly into the Item.
ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_favorite) { ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_favorite) {
String conf = p_path.path_join("project.godot"); String conf = p_path.path_join("project.godot");
bool grayed = false; bool grayed = false;
@ -1853,7 +1662,7 @@ void ProjectList::erase_selected_projects(bool p_delete_project_contents) {
update_dock_menu(); update_dock_menu();
} }
// Draws selected project highlight // Draws selected project highlight.
void ProjectList::_panel_draw(Node *p_hb) { void ProjectList::_panel_draw(Node *p_hb) {
Control *hb = Object::cast_to<Control>(p_hb); Control *hb = Object::cast_to<Control>(p_hb);
@ -1870,7 +1679,7 @@ void ProjectList::_panel_draw(Node *p_hb) {
} }
} }
// Input for each item in the list // Input for each item in the list.
void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) { void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
Ref<InputEventMouseButton> mb = p_ev; Ref<InputEventMouseButton> mb = p_ev;
int clicked_index = p_hb->get_index(); int clicked_index = p_hb->get_index();
@ -1940,14 +1749,21 @@ void ProjectList::_show_project(const String &p_path) {
OS::get_singleton()->shell_open(String("file://") + p_path); OS::get_singleton()->shell_open(String("file://") + p_path);
} }
const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
void ProjectList::_bind_methods() { void ProjectList::_bind_methods() {
ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED)); ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN)); ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN));
} }
ProjectList::ProjectList() {
_scroll_children = memnew(VBoxContainer);
_scroll_children->set_h_size_flags(Control::SIZE_EXPAND_FILL);
add_child(_scroll_children);
_config_path = EditorPaths::get_singleton()->get_data_dir().path_join("projects.cfg");
}
/// Project Manager.
ProjectManager *ProjectManager::singleton = nullptr; ProjectManager *ProjectManager::singleton = nullptr;
void ProjectManager::_notification(int p_what) { void ProjectManager::_notification(int p_what) {
@ -2438,6 +2254,7 @@ void ProjectManager::_scan_dir(const String &path) {
} }
da->list_dir_end(); da->list_dir_end();
} }
void ProjectManager::_scan_begin(const String &p_base) { void ProjectManager::_scan_begin(const String &p_base) {
print_line("Scanning projects at: " + p_base); print_line("Scanning projects at: " + p_base);
_scan_dir(p_base); _scan_dir(p_base);

View File

@ -31,21 +31,254 @@
#ifndef PROJECT_MANAGER_H #ifndef PROJECT_MANAGER_H
#define PROJECT_MANAGER_H #define PROJECT_MANAGER_H
#include "core/io/config_file.h"
#include "editor/editor_about.h" #include "editor/editor_about.h"
#include "editor/plugins/asset_library_editor_plugin.h"
#include "scene/gui/dialogs.h" #include "scene/gui/dialogs.h"
#include "scene/gui/file_dialog.h" #include "scene/gui/file_dialog.h"
#include "scene/gui/scroll_container.h" #include "scene/gui/scroll_container.h"
#include "scene/gui/tree.h"
class CheckBox; class CheckBox;
class ProjectDialog; class EditorAssetLibrary;
class ProjectList; class EditorFileDialog;
enum FilterOption { class ProjectDialog : public ConfirmationDialog {
GDCLASS(ProjectDialog, ConfirmationDialog);
public:
enum Mode {
MODE_NEW,
MODE_IMPORT,
MODE_INSTALL,
MODE_RENAME,
};
private:
enum MessageType {
MESSAGE_ERROR,
MESSAGE_WARNING,
MESSAGE_SUCCESS,
};
enum InputType {
PROJECT_PATH,
INSTALL_PATH,
};
Mode mode = MODE_NEW;
bool is_folder_empty = true;
Button *browse = nullptr;
Button *install_browse = nullptr;
Button *create_dir = nullptr;
Container *name_container = nullptr;
Container *path_container = nullptr;
Container *install_path_container = nullptr;
Container *renderer_container = nullptr;
Label *renderer_info = nullptr;
HBoxContainer *default_files_container = nullptr;
Ref<ButtonGroup> renderer_button_group;
Label *msg = nullptr;
LineEdit *project_path = nullptr;
LineEdit *project_name = nullptr;
LineEdit *install_path = nullptr;
TextureRect *status_rect = nullptr;
TextureRect *install_status_rect = nullptr;
OptionButton *vcs_metadata_selection = nullptr;
EditorFileDialog *fdialog = nullptr;
EditorFileDialog *fdialog_install = nullptr;
AcceptDialog *dialog_error = nullptr;
String zip_path;
String zip_title;
String fav_dir;
String created_folder_path;
void _set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS, InputType input_type = PROJECT_PATH);
String _test_path();
void _path_text_changed(const String &p_path);
void _path_selected(const String &p_path);
void _file_selected(const String &p_path);
void _install_path_selected(const String &p_path);
void _browse_path();
void _browse_install_path();
void _create_folder();
void _text_changed(const String &p_text);
void _nonempty_confirmation_ok_pressed();
void _renderer_selected();
void _remove_created_folder();
void ok_pressed() override;
void cancel_pressed() override;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_zip_path(const String &p_path);
void set_zip_title(const String &p_title);
void set_mode(Mode p_mode);
void set_project_path(const String &p_path);
void show_dialog();
ProjectDialog();
};
class ProjectListItemControl : public HBoxContainer {
GDCLASS(ProjectListItemControl, HBoxContainer)
friend class ProjectList;
TextureButton *favorite_button = nullptr;
TextureRect *icon = nullptr;
bool icon_needs_reload = true;
bool hover = false;
protected:
void _notification(int p_what);
public:
void set_is_favorite(bool fav);
ProjectListItemControl();
};
class ProjectList : public ScrollContainer {
GDCLASS(ProjectList, ScrollContainer)
friend class ProjectManager;
public:
enum FilterOption {
EDIT_DATE, EDIT_DATE,
NAME, NAME,
PATH, PATH,
};
// Can often be passed by copy
struct Item {
String project_name;
String description;
String path;
String icon;
String main_scene;
PackedStringArray unsupported_features;
uint64_t last_edited = 0;
bool favorite = false;
bool grayed = false;
bool missing = false;
int version = 0;
ProjectListItemControl *control = nullptr;
Item() {}
Item(const String &p_name,
const String &p_description,
const String &p_path,
const String &p_icon,
const String &p_main_scene,
const PackedStringArray &p_unsupported_features,
uint64_t p_last_edited,
bool p_favorite,
bool p_grayed,
bool p_missing,
int p_version) {
project_name = p_name;
description = p_description;
path = p_path;
icon = p_icon;
main_scene = p_main_scene;
unsupported_features = p_unsupported_features;
last_edited = p_last_edited;
favorite = p_favorite;
grayed = p_grayed;
missing = p_missing;
version = p_version;
control = nullptr;
}
_FORCE_INLINE_ bool operator==(const Item &l) const {
return path == l.path;
}
};
private:
bool project_opening_initiated = false;
String _search_term;
FilterOption _order_option = FilterOption::EDIT_DATE;
HashSet<String> _selected_project_paths;
String _last_clicked; // Project key
VBoxContainer *_scroll_children = nullptr;
int _icon_load_index = 0;
Vector<Item> _projects;
ConfigFile _config;
String _config_path;
void _panel_draw(Node *p_hb);
void _panel_input(const Ref<InputEvent> &p_ev, Node *p_hb);
void _favorite_pressed(Node *p_hb);
void _show_project(const String &p_path);
void select_range(int p_begin, int p_end);
void toggle_select(int p_index);
void create_project_item_control(int p_index);
void remove_project(int p_index, bool p_update_settings);
void update_icons_async();
void load_project_icon(int p_index);
static Item load_project_data(const String &p_property_key, bool p_favorite);
void _global_menu_new_window(const Variant &p_tag);
void _global_menu_open_project(const Variant &p_tag);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
static const char *SIGNAL_SELECTION_CHANGED;
static const char *SIGNAL_PROJECT_ASK_OPEN;
void load_projects();
int get_project_count() const;
void sort_projects();
void add_project(const String &dir_path, bool favorite);
void set_project_version(const String &p_project_path, int version);
int refresh_project(const String &dir_path);
void ensure_project_visible(int p_index);
void select_project(int p_index);
void select_first_visible_project();
void erase_selected_projects(bool p_delete_project_contents);
Vector<Item> get_selected_projects() const;
const HashSet<String> &get_selected_project_keys() const;
int get_single_selected_index() const;
bool is_any_project_missing() const;
void erase_missing_projects();
void set_search_term(String p_search_term);
void set_order_option(int p_option);
void update_dock_menu();
void migrate_config();
void save_config();
ProjectList();
}; };
class ProjectManager : public Control { class ProjectManager : public Control {