فهرست منبع

Merge pull request #112733 from KoBeWi/press_right_to_context

Add a right click menu to the project manager
Thaddeus Crews 1 ماه پیش
والد
کامیت
bd2ca13c6f

+ 117 - 27
editor/project_manager/project_list.cpp

@@ -44,15 +44,12 @@
 #include "scene/gui/dialogs.h"
 #include "scene/gui/label.h"
 #include "scene/gui/line_edit.h"
+#include "scene/gui/popup_menu.h"
 #include "scene/gui/progress_bar.h"
 #include "scene/gui/texture_button.h"
 #include "scene/gui/texture_rect.h"
 #include "scene/resources/image_texture.h"
 
-const char *ProjectList::SIGNAL_LIST_CHANGED = "list_changed";
-const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
-const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
-
 void ProjectListItemControl::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_THEME_CHANGED: {
@@ -86,6 +83,10 @@ void ProjectListItemControl::_notification(int p_what) {
 				explore_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
 #endif
 			}
+
+			if (touch_menu_button) {
+				touch_menu_button->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
+			}
 		} break;
 
 		case NOTIFICATION_MOUSE_ENTER: {
@@ -212,6 +213,10 @@ void ProjectListItemControl::_explore_button_pressed() {
 	emit_signal(SNAME("explore_pressed"));
 }
 
+void ProjectListItemControl::_request_menu() {
+	emit_signal(SNAME("request_menu"), Vector2(touch_menu_button->get_position()));
+}
+
 void ProjectListItemControl::set_project_title(const String &p_title) {
 	project_title->set_text(p_title);
 	project_title->set_accessibility_name(TTRC("Project Name"));
@@ -339,6 +344,7 @@ void ProjectListItemControl::set_is_grayed(bool p_grayed) {
 void ProjectListItemControl::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("favorite_pressed"));
 	ADD_SIGNAL(MethodInfo("explore_pressed"));
+	ADD_SIGNAL(MethodInfo("request_menu"));
 }
 
 ProjectListItemControl::ProjectListItemControl() {
@@ -442,6 +448,14 @@ ProjectListItemControl::ProjectListItemControl() {
 		spacer->set_custom_minimum_size(Size2(10, 10));
 		path_hb->add_child(spacer);
 	}
+
+	if (DisplayServer::get_singleton()->is_touchscreen_available()) {
+		touch_menu_button = memnew(Button);
+		touch_menu_button->set_theme_type_variation(SceneStringName(FlatButton));
+		touch_menu_button->set_v_size_flags(SIZE_SHRINK_CENTER);
+		add_child(touch_menu_button);
+		touch_menu_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectListItemControl::_request_menu));
+	}
 }
 
 struct ProjectListComparator {
@@ -485,6 +499,12 @@ void ProjectList::_notification(int p_what) {
 			}
 		} break;
 
+		case NOTIFICATION_THEME_CHANGED: {
+			if (project_context_menu) {
+				_update_menu_icons();
+			}
+		} break;
+
 		case NOTIFICATION_PROCESS: {
 			// Load icons as a coroutine to speed up launch when you have hundreds of projects.
 			if (_icon_load_index < _projects.size()) {
@@ -1028,6 +1048,7 @@ void ProjectList::_create_project_item_control(int p_index) {
 #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
 	hb->connect("explore_pressed", callable_mp(this, &ProjectList::_on_explore_pressed).bind(item.path));
 #endif
+	hb->connect("request_menu", callable_mp(this, &ProjectList::_open_menu).bind(hb));
 
 	project_list_vbox->add_child(hb);
 	item.control = hb;
@@ -1066,38 +1087,42 @@ void ProjectList::_remove_project(int p_index, bool p_update_config) {
 	update_dock_menu();
 }
 
-void ProjectList::_list_item_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
+void ProjectList::_list_item_input(const Ref<InputEvent> &p_ev, Control *p_hb) {
 	Ref<InputEventMouseButton> mb = p_ev;
 	int clicked_index = p_hb->get_index();
 	const Item &clicked_project = _projects[clicked_index];
 
-	if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
-		if (mb->is_shift_pressed() && _selected_project_paths.size() > 0 && !_last_clicked.is_empty() && clicked_project.path != _last_clicked) {
-			int anchor_index = -1;
-			for (int i = 0; i < _projects.size(); ++i) {
-				const Item &p = _projects[i];
-				if (p.path == _last_clicked) {
-					anchor_index = p.control->get_index();
-					break;
+	if (mb.is_valid() && mb->is_pressed()) {
+		if (mb->get_button_index() == MouseButton::LEFT) {
+			if (mb->is_shift_pressed() && _selected_project_paths.size() > 0 && !_last_clicked.is_empty() && clicked_project.path != _last_clicked) {
+				int anchor_index = -1;
+				for (int i = 0; i < _projects.size(); ++i) {
+					const Item &p = _projects[i];
+					if (p.path == _last_clicked) {
+						anchor_index = p.control->get_index();
+						break;
+					}
 				}
-			}
-			CRASH_COND(anchor_index == -1);
-			_select_project_range(anchor_index, clicked_index);
+				CRASH_COND(anchor_index == -1);
+				_select_project_range(anchor_index, clicked_index);
 
-		} else if (mb->is_command_or_control_pressed()) {
-			_toggle_project(clicked_index);
+			} else if (mb->is_command_or_control_pressed()) {
+				_toggle_project(clicked_index);
 
-		} else {
-			_last_clicked = clicked_project.path;
-			select_project(clicked_index);
-		}
+			} else {
+				_last_clicked = clicked_project.path;
+				select_project(clicked_index);
+			}
 
-		emit_signal(SNAME(SIGNAL_SELECTION_CHANGED));
+			emit_signal(SNAME(SIGNAL_SELECTION_CHANGED));
 
-		// Do not allow opening a project more than once using a single project manager instance.
-		// Opening the same project in several editor instances at once can lead to various issues.
-		if (!mb->is_command_or_control_pressed() && mb->is_double_click() && !project_opening_initiated) {
-			emit_signal(SNAME(SIGNAL_PROJECT_ASK_OPEN));
+			// Do not allow opening a project more than once using a single project manager instance.
+			// Opening the same project in several editor instances at once can lead to various issues.
+			if (!mb->is_command_or_control_pressed() && mb->is_double_click() && !project_opening_initiated) {
+				emit_signal(SNAME(SIGNAL_PROJECT_ASK_OPEN));
+			}
+		} else if (mb->get_button_index() == MouseButton::RIGHT) {
+			_open_menu(mb->get_position(), p_hb);
 		}
 	}
 
@@ -1154,6 +1179,70 @@ void ProjectList::_on_explore_pressed(const String &p_path) {
 	OS::get_singleton()->shell_show_in_file_manager(p_path, true);
 }
 
+void ProjectList::_open_menu(const Vector2 &p_at, Control *p_hb) {
+	int clicked_index = p_hb->get_index();
+	const Item &clicked_project = _projects[clicked_index];
+
+	if (!project_context_menu) {
+		project_context_menu = memnew(PopupMenu);
+		project_context_menu->add_item(TTRC("Open in Editor"), MENU_EDIT);
+		project_context_menu->add_item(TTRC("Open in Editor (Verbose Mode)"), MENU_EDIT_VERBOSE);
+		project_context_menu->add_item(TTRC("Open in Editor (Recovery Mode)"), MENU_EDIT_RECOVERY);
+		project_context_menu->add_item(TTRC("Run Project"), MENU_RUN);
+		project_context_menu->add_separator();
+#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
+		project_context_menu->add_item(TTRC("Show in File Manager"), MENU_SHOW_IN_FILE_MANAGER);
+#endif
+		project_context_menu->add_item(TTRC("Copy Path"), MENU_COPY_PATH);
+		project_context_menu->add_separator();
+		project_context_menu->add_item(TTRC("Rename"), MENU_RENAME);
+		project_context_menu->add_item(TTRC("Manage Tags"), MENU_MANAGE_TAGS);
+		project_context_menu->add_item(TTRC("Duplicate"), MENU_DUPLICATE);
+		project_context_menu->add_item(TTRC("Remove from Project List"), MENU_REMOVE);
+		add_child(project_context_menu);
+		project_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &ProjectList::_menu_option));
+		_update_menu_icons();
+	}
+	select_project(clicked_index);
+
+	for (int id : Vector<int>{
+				 MENU_EDIT,
+				 MENU_EDIT_VERBOSE,
+				 MENU_EDIT_RECOVERY,
+				 MENU_RUN,
+#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
+				 MENU_SHOW_IN_FILE_MANAGER,
+#endif
+				 MENU_RENAME,
+				 MENU_MANAGE_TAGS,
+				 MENU_DUPLICATE }) {
+		project_context_menu->set_item_disabled(project_context_menu->get_item_index(id), clicked_project.missing);
+	}
+
+	project_context_menu->set_position(p_hb->get_screen_position() + p_at);
+	project_context_menu->reset_size();
+	project_context_menu->popup();
+}
+
+void ProjectList::_menu_option(int p_option) {
+	emit_signal(SIGNAL_MENU_OPTION_SELECTED, p_option);
+}
+
+void ProjectList::_update_menu_icons() {
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_EDIT), get_editor_theme_icon("Edit"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_EDIT_VERBOSE), get_editor_theme_icon("Notification"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_EDIT_RECOVERY), get_editor_theme_icon("NodeWarning"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_RUN), get_editor_theme_icon("Play"));
+#if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED)
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_SHOW_IN_FILE_MANAGER), get_editor_theme_icon("Load"));
+#endif
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_COPY_PATH), get_editor_theme_icon("ActionCopy"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_RENAME), get_editor_theme_icon("Rename"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_MANAGE_TAGS), get_editor_theme_icon("Script"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_DUPLICATE), get_editor_theme_icon("Duplicate"));
+	project_context_menu->set_item_icon(project_context_menu->get_item_index(MENU_REMOVE), get_editor_theme_icon("Remove"));
+}
+
 // Project list selection.
 
 void ProjectList::_clear_project_selection() {
@@ -1411,6 +1500,7 @@ void ProjectList::_bind_methods() {
 	ADD_SIGNAL(MethodInfo(SIGNAL_LIST_CHANGED));
 	ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
 	ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN));
+	ADD_SIGNAL(MethodInfo(SIGNAL_MENU_OPTION_SELECTED));
 }
 
 ProjectList::ProjectList() {

+ 26 - 4
editor/project_manager/project_list.h

@@ -38,6 +38,7 @@
 class AcceptDialog;
 class Button;
 class Label;
+class PopupMenu;
 class ProjectList;
 class TextureButton;
 class TextureRect;
@@ -56,6 +57,7 @@ class ProjectListItemControl : public HBoxContainer {
 	Label *project_version = nullptr;
 	TextureRect *project_unsupported_features = nullptr;
 	HBoxContainer *tag_container = nullptr;
+	Button *touch_menu_button = nullptr;
 
 	Color favorite_focus_color;
 
@@ -68,6 +70,7 @@ class ProjectListItemControl : public HBoxContainer {
 	void _update_favorite_button_focus_color();
 	void _favorite_button_pressed();
 	void _explore_button_pressed();
+	void _request_menu();
 
 	ProjectList *get_list() const;
 
@@ -113,6 +116,19 @@ public:
 		TAGS,
 	};
 
+	enum MenuOption {
+		MENU_EDIT,
+		MENU_EDIT_VERBOSE,
+		MENU_EDIT_RECOVERY,
+		MENU_RUN,
+		MENU_SHOW_IN_FILE_MANAGER,
+		MENU_COPY_PATH,
+		MENU_RENAME,
+		MENU_MANAGE_TAGS,
+		MENU_DUPLICATE,
+		MENU_REMOVE,
+	};
+
 	// Can often be passed by copy.
 	struct Item {
 		String project_name;
@@ -200,6 +216,7 @@ private:
 	String _last_clicked; // Project key
 
 	VBoxContainer *project_list_vbox = nullptr;
+	PopupMenu *project_context_menu = nullptr;
 
 	// Projects scan.
 
@@ -233,10 +250,14 @@ private:
 	void _toggle_project(int p_index);
 	void _remove_project(int p_index, bool p_update_settings);
 
-	void _list_item_input(const Ref<InputEvent> &p_ev, Node *p_hb);
+	void _list_item_input(const Ref<InputEvent> &p_ev, Control *p_hb);
 	void _on_favorite_pressed(Node *p_hb);
 	void _on_explore_pressed(const String &p_path);
 
+	void _open_menu(const Vector2 &p_at, Control *p_hb);
+	void _menu_option(int p_option);
+	void _update_menu_icons();
+
 	// Project list selection.
 
 	void _clear_project_selection();
@@ -254,9 +275,10 @@ protected:
 	static void _bind_methods();
 
 public:
-	static const char *SIGNAL_LIST_CHANGED;
-	static const char *SIGNAL_SELECTION_CHANGED;
-	static const char *SIGNAL_PROJECT_ASK_OPEN;
+	static inline const char *SIGNAL_LIST_CHANGED = "list_changed";
+	static inline const char *SIGNAL_SELECTION_CHANGED = "selection_changed";
+	static inline const char *SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
+	static inline const char *SIGNAL_MENU_OPTION_SELECTED = "menu_option_selected";
 
 	static bool project_feature_looks_like_version(const String &p_feature);
 

+ 50 - 8
editor/project_manager/project_manager.cpp

@@ -265,7 +265,6 @@ void ProjectManager::_update_theme(bool p_skip_creation) {
 			rename_btn->set_button_icon(get_editor_theme_icon("Rename"));
 			duplicate_btn->set_button_icon(get_editor_theme_icon("Duplicate"));
 			manage_tags_btn->set_button_icon(get_editor_theme_icon("Script"));
-			show_in_fm_btn->set_button_icon(get_editor_theme_icon("Load"));
 			erase_btn->set_button_icon(get_editor_theme_icon("Remove"));
 			erase_missing_btn->set_button_icon(get_editor_theme_icon("Clear"));
 			create_tag_btn->set_button_icon(get_editor_theme_icon("Add"));
@@ -283,7 +282,6 @@ void ProjectManager::_update_theme(bool p_skip_creation) {
 			rename_btn->add_theme_constant_override("h_separation", h_separation);
 			duplicate_btn->add_theme_constant_override("h_separation", h_separation);
 			manage_tags_btn->add_theme_constant_override("h_separation", h_separation);
-			show_in_fm_btn->add_theme_constant_override("h_separation", h_separation);
 			erase_btn->add_theme_constant_override("h_separation", h_separation);
 			erase_missing_btn->add_theme_constant_override("h_separation", h_separation);
 
@@ -395,6 +393,55 @@ void ProjectManager::_open_asset_library_confirmed() {
 	_select_main_view(MAIN_VIEW_ASSETLIB);
 }
 
+void ProjectManager::_project_list_menu_option(int p_option) {
+	switch (p_option) {
+		case ProjectList::MENU_EDIT:
+			_open_selected_projects();
+			break;
+
+		case ProjectList::MENU_EDIT_VERBOSE:
+			open_in_verbose_mode = true;
+			_open_selected_projects_check_warnings();
+			break;
+
+		case ProjectList::MENU_EDIT_RECOVERY:
+			_open_recovery_mode_ask(true);
+			break;
+
+		case ProjectList::MENU_RUN:
+			_run_project_confirm();
+			break;
+
+		case ProjectList::MENU_SHOW_IN_FILE_MANAGER:
+			_show_project_in_file_manager();
+			break;
+
+		case ProjectList::MENU_COPY_PATH: {
+			const Vector<ProjectList::Item> &selected_list = project_list->get_selected_projects();
+			if (selected_list.is_empty()) {
+				return;
+			}
+			DisplayServer::get_singleton()->clipboard_set(selected_list[0].path);
+		} break;
+
+		case ProjectList::MENU_RENAME:
+			_rename_project();
+			break;
+
+		case ProjectList::MENU_MANAGE_TAGS:
+			_manage_project_tags();
+			break;
+
+		case ProjectList::MENU_DUPLICATE:
+			_duplicate_project();
+			break;
+
+		case ProjectList::MENU_REMOVE:
+			_erase_project();
+			break;
+	}
+}
+
 void ProjectManager::_show_error(const String &p_message, const Size2 &p_min_size) {
 	error_dialog->set_text(p_message);
 	error_dialog->popup_centered(p_min_size);
@@ -807,7 +854,6 @@ void ProjectManager::_update_project_buttons() {
 	rename_btn->set_disabled(empty_selection || is_missing_project_selected);
 	duplicate_btn->set_disabled(empty_selection || is_missing_project_selected);
 	manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1);
-	show_in_fm_btn->set_disabled(empty_selection || is_missing_project_selected);
 	run_btn->set_disabled(empty_selection || is_missing_project_selected);
 
 	erase_missing_btn->set_disabled(!project_list->is_any_project_missing());
@@ -1514,6 +1560,7 @@ ProjectManager::ProjectManager() {
 			project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_list_placeholder));
 			project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
 			project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));
+			project_list->connect(ProjectList::SIGNAL_MENU_OPTION_SELECTED, callable_mp(this, &ProjectManager::_project_list_menu_option));
 
 			// Empty project list placeholder.
 			{
@@ -1621,11 +1668,6 @@ ProjectManager::ProjectManager() {
 			manage_tags_btn->set_shortcut(ED_SHORTCUT("project_manager/project_tags", TTRC("Manage Tags"), KeyModifierMask::CMD_OR_CTRL | Key::T));
 			project_list_sidebar->add_child(manage_tags_btn);
 
-			show_in_fm_btn = memnew(Button);
-			show_in_fm_btn->set_text(TTRC("Show in File Manager"));
-			show_in_fm_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_show_project_in_file_manager));
-			project_list_sidebar->add_child(show_in_fm_btn);
-
 			erase_btn = memnew(Button);
 			erase_btn->set_text(TTRC("Remove"));
 			erase_btn->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTRC("Remove Project"), Key::KEY_DELETE));

+ 1 - 1
editor/project_manager/project_manager.h

@@ -118,6 +118,7 @@ class ProjectManager : public Control {
 
 	void _show_about();
 	void _open_asset_library_confirmed();
+	void _project_list_menu_option(int p_option);
 
 	AcceptDialog *error_dialog = nullptr;
 
@@ -160,7 +161,6 @@ class ProjectManager : public Control {
 	Button *rename_btn = nullptr;
 	Button *duplicate_btn = nullptr;
 	Button *manage_tags_btn = nullptr;
-	Button *show_in_fm_btn = nullptr;
 	Button *erase_btn = nullptr;
 	Button *erase_missing_btn = nullptr;
 	Button *donate_btn = nullptr;