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_themes.h"
#include "editor/editor_vcs_interface.h"
#include "editor/plugins/asset_library_editor_plugin.h"
#include "main/main.h"
#include "scene/gui/center_container.h"
#include "scene/gui/check_box.h"
@ -62,58 +63,9 @@
constexpr int GODOT4_CONFIG_VERSION = 5;
class ProjectDialog : public ConfirmationDialog {
GDCLASS(ProjectDialog, ConfirmationDialog);
/// Project Dialog.
public:
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) {
void ProjectDialog::_set_message(const String &p_msg, MessageType p_type, InputType input_type) {
msg->set_text(p_msg);
Ref<Texture2D> current_path_icon = status_rect->get_texture();
Ref<Texture2D> current_install_icon = install_status_rect->get_texture();
@ -152,7 +104,7 @@ private:
}
}
String _test_path() {
String ProjectDialog::_test_path() {
Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
String valid_path, valid_install_path;
if (d->change_dir(project_path->get_text()) == OK) {
@ -170,7 +122,7 @@ private:
}
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);
return "";
}
@ -183,7 +135,7 @@ private:
}
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);
return "";
}
@ -197,7 +149,7 @@ private:
unzFile pkg = unzOpen2(valid_path.utf8().get_data(), &io);
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);
unzClose(pkg);
return "";
@ -220,7 +172,7 @@ private:
}
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);
unzClose(pkg);
return "";
@ -246,20 +198,20 @@ private:
d->list_dir_end();
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);
return "";
}
} 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();
get_ok_button()->set_disabled(true);
return "";
}
} 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);
return "";
}
@ -284,24 +236,24 @@ private:
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()) {
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);
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);
return valid_path;
}
}
set_message("");
set_message("", MESSAGE_SUCCESS, INSTALL_PATH);
_set_message("");
_set_message("", MESSAGE_SUCCESS, INSTALL_PATH);
get_ok_button()->set_disabled(false);
return valid_path;
}
void _path_text_changed(const String &p_path) {
void ProjectDialog::_path_text_changed(const String &p_path) {
String sp = _test_path();
if (!sp.is_empty()) {
// If the project name is empty or default, infer the project name from the selected folder name
@ -326,7 +278,7 @@ private:
}
}
void _file_selected(const String &p_path) {
void ProjectDialog::_file_selected(const String &p_path) {
String p = p_path;
if (mode == MODE_IMPORT) {
if (p.ends_with("project.godot")) {
@ -338,7 +290,7 @@ private:
install_path_container->show();
get_ok_button()->set_disabled(false);
} 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);
return;
}
@ -354,21 +306,21 @@ private:
}
}
void _path_selected(const String &p_path) {
void ProjectDialog::_path_selected(const String &p_path) {
String sp = p_path.simplify_path();
project_path->set_text(sp);
_path_text_changed(sp);
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();
install_path->set_text(sp);
_path_text_changed(sp);
get_ok_button()->call_deferred(SNAME("grab_focus"));
}
void _browse_path() {
void ProjectDialog::_browse_path() {
fdialog->set_current_dir(project_path->get_text());
if (mode == MODE_IMPORT) {
@ -382,16 +334,16 @@ private:
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_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);
fdialog_install->popup_file_dialog();
}
void _create_folder() {
void ProjectDialog::_create_folder() {
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(".")) {
set_message(TTR("Invalid project name."), MESSAGE_WARNING);
_set_message(TTR("Invalid project name."), MESSAGE_WARNING);
return;
}
@ -416,7 +368,7 @@ private:
}
}
void _text_changed(const String &p_text) {
void ProjectDialog::_text_changed(const String &p_text) {
if (mode != MODE_NEW) {
return;
}
@ -424,16 +376,16 @@ private:
_test_path();
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;
ok_pressed();
}
void _renderer_selected() {
void ProjectDialog::_renderer_selected() {
String renderer_type = renderer_button_group->get_pressed_button()->get_meta(SNAME("rendering_method"));
if (renderer_type == "forward_plus") {
@ -462,13 +414,23 @@ private:
}
}
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();
if (mode == MODE_RENAME) {
String dir2 = _test_path();
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;
}
@ -477,12 +439,12 @@ private:
String project_godot = dir2.path_join("project.godot");
Error err = cfg.load(project_godot);
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 {
cfg.set_value("application", "config/name", project_name->get_text().strip_edges());
err = cfg.save(project_godot);
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";
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 {
// Store default project icon in SVG format.
Error err;
@ -550,7 +512,7 @@ private:
fa_icon->store_string(get_default_project_icon());
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);
@ -665,17 +627,7 @@ private:
}
}
void _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 cancel_pressed() override {
void ProjectDialog::cancel_pressed() {
_remove_created_folder();
project_path->clear();
@ -692,37 +644,23 @@ private:
}
}
void _notification(int p_what) {
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) {
void ProjectDialog::set_zip_path(const String &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;
}
void set_mode(Mode p_mode) {
void ProjectDialog::set_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);
}
void show_dialog() {
void ProjectDialog::show_dialog() {
if (mode == MODE_RENAME) {
project_path->set_editable(false);
browse->hide();
@ -744,7 +682,7 @@ public:
String project_godot = project_path->get_text().path_join("project.godot");
Error err = cfg.load(project_godot);
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();
msg->show();
get_ok_button()->set_disabled(true);
@ -818,7 +756,20 @@ public:
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);
add_child(vb);
@ -1005,35 +956,14 @@ public:
fdialog_install->connect("file_selected", callable_mp(this, &ProjectDialog::_install_path_selected));
set_hide_on_ok(false);
mode = MODE_NEW;
dialog_error = memnew(AcceptDialog);
add_child(dialog_error);
}
};
class ProjectListItemControl : public HBoxContainer {
GDCLASS(ProjectListItemControl, HBoxContainer)
public:
TextureButton *favorite_button;
TextureRect *icon;
bool icon_needs_reload;
bool hover;
/// Project List and friends.
ProjectListItemControl() {
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) {
void ProjectListItemControl::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: {
hover = true;
@ -1052,128 +982,17 @@ public:
} break;
}
}
};
class ProjectList : public ScrollContainer {
GDCLASS(ProjectList, ScrollContainer)
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_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;
void ProjectListItemControl::set_is_favorite(bool fav) {
favorite_button->set_modulate(fav ? Color(1, 1, 1, 1) : Color(1, 1, 1, 0.2));
}
_FORCE_INLINE_ bool operator==(const Item &l) const {
return path == l.path;
ProjectListItemControl::ProjectListItemControl() {
set_focus_mode(FocusMode::FOCUS_ALL);
}
};
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 {
FilterOption order_option = FilterOption::EDIT_DATE;
ProjectList::FilterOption order_option = ProjectList::FilterOption::EDIT_DATE;
// operator<
_FORCE_INLINE_ bool operator()(const ProjectList::Item &a, const ProjectList::Item &b) const {
@ -1184,9 +1003,9 @@ struct ProjectListComparator {
return false;
}
switch (order_option) {
case PATH:
case ProjectList::PATH:
return a.path < b.path;
case EDIT_DATE:
case ProjectList::EDIT_DATE:
return a.last_edited > b.last_edited;
default:
return a.project_name < b.project_name;
@ -1194,24 +1013,8 @@ struct ProjectListComparator {
}
};
ProjectList::ProjectList() {
_order_option = FilterOption::EDIT_DATE;
_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);
}
const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
void ProjectList::_notification(int 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) {
Item &item = _projects.write[p_index];
@ -1259,7 +1067,8 @@ void ProjectList::load_project_icon(int p_index) {
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) {
String conf = p_path.path_join("project.godot");
bool grayed = false;
@ -1853,7 +1662,7 @@ void ProjectList::erase_selected_projects(bool p_delete_project_contents) {
update_dock_menu();
}
// Draws selected project highlight
// Draws selected project highlight.
void ProjectList::_panel_draw(Node *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) {
Ref<InputEventMouseButton> mb = p_ev;
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);
}
const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
void ProjectList::_bind_methods() {
ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
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;
void ProjectManager::_notification(int p_what) {
@ -2438,6 +2254,7 @@ void ProjectManager::_scan_dir(const String &path) {
}
da->list_dir_end();
}
void ProjectManager::_scan_begin(const String &p_base) {
print_line("Scanning projects at: " + p_base);
_scan_dir(p_base);

View File

@ -31,23 +31,256 @@
#ifndef PROJECT_MANAGER_H
#define PROJECT_MANAGER_H
#include "core/io/config_file.h"
#include "editor/editor_about.h"
#include "editor/plugins/asset_library_editor_plugin.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/file_dialog.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/tree.h"
class CheckBox;
class ProjectDialog;
class ProjectList;
class EditorAssetLibrary;
class EditorFileDialog;
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,
NAME,
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 {
GDCLASS(ProjectManager, Control);