From 6f7411a8b6952dabfe00a41b066e66f72b50b185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E9=9D=92=E5=B1=B1?= Date: Sat, 4 Jan 2025 22:30:20 +0800 Subject: [PATCH] Improve Project Manager UI navigation Provides a focus stylebox for navigable controls. The focused project is automatically selected. This resolves the conflict in the handling of `Key::UP`/`Key::DOWN` in `ProjectManager::shortcut_input()` and `Viewport::_gui_input_event()`. --- editor/project_manager.cpp | 35 +-------------- editor/project_manager/project_list.cpp | 57 ++++++++++++++++++++----- editor/project_manager/project_list.h | 6 ++- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index c3bd3acf775..0f7eb7818ef 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -804,9 +804,7 @@ void ProjectManager::_on_project_created(const String &dir, bool edit) { search_box->clear(); int i = project_list->refresh_project(dir); - project_list->select_project(i); project_list->ensure_project_visible(i); - _update_project_buttons(); _update_list_placeholder(); if (edit) { @@ -1059,42 +1057,13 @@ void ProjectManager::shortcut_input(const Ref &p_ev) { } break; case Key::HOME: { if (project_list->get_project_count() > 0) { - project_list->select_project(0); - _update_project_buttons(); + project_list->ensure_project_visible(0); } } break; case Key::END: { if (project_list->get_project_count() > 0) { - project_list->select_project(project_list->get_project_count() - 1); - _update_project_buttons(); - } - - } break; - case Key::UP: { - if (k->is_shift_pressed()) { - break; - } - - int index = project_list->get_single_selected_index(); - if (index > 0) { - project_list->select_project(index - 1); - project_list->ensure_project_visible(index - 1); - _update_project_buttons(); - } - - break; - } - case Key::DOWN: { - if (k->is_shift_pressed()) { - break; - } - - int index = project_list->get_single_selected_index(); - if (index + 1 < project_list->get_project_count()) { - project_list->select_project(index + 1); - project_list->ensure_project_visible(index + 1); - _update_project_buttons(); + project_list->ensure_project_visible(project_list->get_project_count() - 1); } } break; diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index a4d3628d179..5cfeca6c8b2 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -67,7 +67,14 @@ void ProjectListItemControl::_notification(int p_what) { project_path->add_theme_color_override(SceneStringName(font_color), get_theme_color(SceneStringName(font_color), SNAME("Tree"))); project_unsupported_features->set_texture(get_editor_theme_icon(SNAME("NodeWarning"))); - favorite_button->set_texture_normal(get_editor_theme_icon(SNAME("Favorites"))); + favorite_focus_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); + _update_favorite_button_focus_color(); + if (is_favourite) { + favorite_button->set_texture_normal(get_editor_theme_icon(SNAME("Favorites"))); + } else { + favorite_button->set_texture_normal(get_editor_theme_icon(SNAME("Unfavorite"))); + } + if (project_is_missing) { explore_button->set_button_icon(get_editor_theme_icon(SNAME("FileBroken"))); } else { @@ -92,12 +99,23 @@ void ProjectListItemControl::_notification(int p_what) { if (is_hovering) { draw_style_box(get_theme_stylebox(SNAME("hovered"), SNAME("Tree")), Rect2(Point2(), get_size())); } + if (has_focus()) { + draw_style_box(get_theme_stylebox(SNAME("focus"), SNAME("Tree")), Rect2(Point2(), get_size())); + } draw_line(Point2(0, get_size().y + 1), Point2(get_size().x, get_size().y + 1), get_theme_color(SNAME("guide_color"), SNAME("Tree"))); } break; } } +void ProjectListItemControl::_update_favorite_button_focus_color() { + if (favorite_button->has_focus()) { + favorite_button->set_self_modulate(favorite_focus_color); + } else { + favorite_button->set_self_modulate(Color(1.0, 1.0, 1.0, 1.0)); + } +} + void ProjectListItemControl::_favorite_button_pressed() { emit_signal(SNAME("favorite_pressed")); } @@ -186,7 +204,12 @@ void ProjectListItemControl::set_selected(bool p_selected) { } void ProjectListItemControl::set_is_favorite(bool p_favorite) { - favorite_button->set_modulate(p_favorite ? Color(1, 1, 1, 1) : Color(1, 1, 1, 0.2)); + is_favourite = p_favorite; + if (p_favorite) { + favorite_button->set_texture_normal(get_editor_theme_icon(SNAME("Favorites"))); + } else { + favorite_button->set_texture_normal(get_editor_theme_icon(SNAME("Unfavorite"))); + } } void ProjectListItemControl::set_is_missing(bool p_missing) { @@ -242,6 +265,8 @@ ProjectListItemControl::ProjectListItemControl() { favorite_button->set_mouse_filter(MOUSE_FILTER_PASS); favorite_box->add_child(favorite_button); favorite_button->connect(SceneStringName(pressed), callable_mp(this, &ProjectListItemControl::_favorite_button_pressed)); + favorite_button->connect(SceneStringName(focus_entered), callable_mp(this, &ProjectListItemControl::_update_favorite_button_focus_color)); + favorite_button->connect(SceneStringName(focus_exited), callable_mp(this, &ProjectListItemControl::_update_favorite_button_focus_color)); project_icon = memnew(TextureRect); project_icon->set_name("ProjectIcon"); @@ -833,7 +858,6 @@ int ProjectList::refresh_project(const String &dir_path) { for (int i = 0; i < _projects.size(); ++i) { if (_projects[i].path == dir_path) { if (was_selected) { - select_project(i); ensure_project_visible(i); } _load_project_icon(i); @@ -849,7 +873,21 @@ int ProjectList::refresh_project(const String &dir_path) { void ProjectList::ensure_project_visible(int p_index) { const Item &item = _projects[p_index]; - ensure_control_visible(item.control); + // Since follow focus is enabled. + item.control->grab_focus(); +} + +void ProjectList::_on_child_focus_entered(ProjectListItemControl *p_hb) { + if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) { + _clear_project_selection(); + + int idx = p_hb->get_index(); + Item &item = _projects.write[idx]; + _selected_project_paths.insert(item.path); + p_hb->set_selected(true); + + emit_signal(SNAME(SIGNAL_SELECTION_CHANGED)); + } } void ProjectList::_create_project_item_control(int p_index) { @@ -876,6 +914,7 @@ void ProjectList::_create_project_item_control(int p_index) { hb->connect(SceneStringName(gui_input), callable_mp(this, &ProjectList::_list_item_input).bind(hb)); hb->connect("favorite_pressed", callable_mp(this, &ProjectList::_on_favorite_pressed).bind(hb)); + hb->connect(SceneStringName(focus_entered), callable_mp(this, &ProjectList::_on_child_focus_entered).bind(hb)); #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED) hb->connect("explore_pressed", callable_mp(this, &ProjectList::_on_explore_pressed).bind(item.path)); @@ -971,12 +1010,8 @@ void ProjectList::_on_favorite_pressed(Node *p_hb) { sort_projects(); if (item.favorite) { - for (int i = 0; i < _projects.size(); ++i) { - if (_projects[i].path == item.path) { - ensure_project_visible(i); - break; - } - } + // Because controls are sorted, the call is delayed in case follow focus does not take effect. + callable_mp((ScrollContainer *)this, &ScrollContainer::ensure_control_visible).call_deferred(control); } update_dock_menu(); @@ -1239,6 +1274,8 @@ void ProjectList::_bind_methods() { } ProjectList::ProjectList() { + set_follow_focus(true); + project_list_vbox = memnew(VBoxContainer); project_list_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); add_child(project_list_vbox); diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 217173017bf..2a31e6ee444 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -57,11 +57,15 @@ class ProjectListItemControl : public HBoxContainer { TextureRect *project_unsupported_features = nullptr; HBoxContainer *tag_container = nullptr; + Color favorite_focus_color; + bool project_is_missing = false; bool icon_needs_reload = true; bool is_selected = false; bool is_hovering = false; + bool is_favourite = false; + void _update_favorite_button_focus_color(); void _favorite_button_pressed(); void _explore_button_pressed(); @@ -207,7 +211,7 @@ private: static void _scan_folder_recursive(const String &p_path, List *r_projects, const SafeFlag &p_scan_active); // Project list items. - + void _on_child_focus_entered(ProjectListItemControl *p_hb); void _create_project_item_control(int p_index); void _toggle_project(int p_index); void _remove_project(int p_index, bool p_update_settings);