Merge pull request #112733 from KoBeWi/press_right_to_context

Add a right click menu to the project manager
This commit is contained in:
Thaddeus Crews
2025-11-13 20:52:27 -06:00
4 changed files with 195 additions and 41 deletions

View File

@ -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);
} else if (mb->is_command_or_control_pressed()) {
_toggle_project(clicked_index);
} else {
_last_clicked = clicked_project.path;
select_project(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);
emit_signal(SNAME(SIGNAL_SELECTION_CHANGED));
} else {
_last_clicked = clicked_project.path;
select_project(clicked_index);
}
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() {