diff --git a/README.md b/README.md index 220076661..59f54e100 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ For other devices we recommend checking out [MinUI](https://github.com/shauninma - Battery Monitoring including history graph and time left prediction - Scrolling animated titles for longer game names - Favorites, added and removed from a context menu (Y) on any listed game +- Global ROM search in the quick menu, with an on-screen keyboard and live results. Reopening it offers your last query, selected, so START runs it again and typing replaces it; L/R move the cursor within the text - Updated and optimized build emulation cores - Rumble strength fixed and is now variable as used by the games - FBNeo Arcade screen rotation diff --git a/skeleton/SYSTEM/res/Search@1x.png b/skeleton/SYSTEM/res/Search@1x.png new file mode 100644 index 000000000..761b936af Binary files /dev/null and b/skeleton/SYSTEM/res/Search@1x.png differ diff --git a/skeleton/SYSTEM/res/Search@2x.png b/skeleton/SYSTEM/res/Search@2x.png new file mode 100644 index 000000000..2452d31a3 Binary files /dev/null and b/skeleton/SYSTEM/res/Search@2x.png differ diff --git a/skeleton/SYSTEM/res/Search@3x.png b/skeleton/SYSTEM/res/Search@3x.png new file mode 100644 index 000000000..1aa95593a Binary files /dev/null and b/skeleton/SYSTEM/res/Search@3x.png differ diff --git a/skeleton/SYSTEM/res/Search@4x.png b/skeleton/SYSTEM/res/Search@4x.png new file mode 100644 index 000000000..476bc92c3 Binary files /dev/null and b/skeleton/SYSTEM/res/Search@4x.png differ diff --git a/workspace/all/common/config.h b/workspace/all/common/config.h index 1c0b333b1..2c9b12df1 100644 --- a/workspace/all/common/config.h +++ b/workspace/all/common/config.h @@ -50,9 +50,10 @@ enum { SCREEN_GAMELIST, SCREEN_GAMESWITCHER, SCREEN_QUICKMENU, - // a view that is only reachable from another screen, never a default view + // views that are only reachable from another screen, never a default view // (keep after the ones above, defaultView persists these as plain ints) SCREEN_CONTEXTMENU, + SCREEN_SEARCH, // meta SCREEN_GAME, SCREEN_OFF diff --git a/workspace/all/common/defines.h b/workspace/all/common/defines.h index 64ab4aabf..62a5ef030 100644 --- a/workspace/all/common/defines.h +++ b/workspace/all/common/defines.h @@ -34,6 +34,10 @@ #define COLLECTIONS_PATH SDCARD_PATH "/Collections" #define LAST_PATH "/tmp/last.txt" // transient +// the last search query, so reopening search offers it again. transient on +// purpose: it should survive launching a game (which exits nextui) but not a +// reboot, where a query from a previous session is just clutter +#define SEARCH_QUERY_PATH "/tmp/search.txt" #define CHANGE_DISC_PATH "/tmp/change_disc.txt" #define RESUME_SLOT_PATH "/tmp/resume_slot.txt" #define NOUI_PATH "/tmp/noui" diff --git a/workspace/all/nextui/nextui.c b/workspace/all/nextui/nextui.c index ba178a579..7ed2a8106 100644 --- a/workspace/all/nextui/nextui.c +++ b/workspace/all/nextui/nextui.c @@ -484,6 +484,10 @@ static int restore_end = 0; static int startgame = 0; /////////////////////////////////////// +// name of the quick menu entry that opens SCREEN_SEARCH. doubles as the icon +// basename in RES_PATH, same as the directory entries beside it +#define QUICK_SEARCH_NAME "Search" + #define MAX_RECENTS 24 // a multiple of all menu rows static void saveRecents(void) { char tmp_path[MAX_PATH]; @@ -938,6 +942,10 @@ static Array* getQuickEntries(void) { Array_push(entries, Entry_new(tools_path, ENTRY_DIR)); } + // global rom search. ENTRY_DIP because it opens a screen of its own rather + // than a directory, see the SCREEN_QUICKMENU input handling + Array_push(entries, Entry_new(QUICK_SEARCH_NAME, ENTRY_DIP)); + return entries; } @@ -1805,6 +1813,9 @@ static void QuickMenu_quit(void) { EntryArray_free(quickActions); } +static void Search_init(void); +static void Search_quit(void); + static void Menu_init(void) { stack = Array_new(); // array of open Directories recents = Array_new(); @@ -1814,6 +1825,7 @@ static void Menu_init(void) { loadLast(); // restore state when available QuickMenu_init(); // needs Menu_init + Search_init(); } static void Menu_quit(void) { RecentArray_free(recents); @@ -1821,6 +1833,7 @@ static void Menu_quit(void) { DirectoryArray_free(stack); QuickMenu_quit(); + Search_quit(); } /////////////////////////////////////// @@ -2559,6 +2572,277 @@ static void applyContextAction(void) { /////////////////////////////////////// +#define SEARCH_QUERY_MAX 32 +#define SEARCH_INDEX_MAX 8192 // a card with more roms than this is not a search problem +#define SEARCH_MAX_DEPTH 4 +#define SEARCH_KEY_ROWS 4 +#define SEARCH_KEY_COLS 10 +#define SEARCH_KEY_W 26 // all before scale +#define SEARCH_KEY_H 16 +#define SEARCH_KEY_GAP 2 + +static const char* search_keys[SEARCH_KEY_ROWS] = { + "1234567890", + "qwertyuiop", + "asdfghjkl-", + "zxcvbnm,.'", +}; + +enum { + SEARCH_FOCUS_KEYBOARD, + SEARCH_FOCUS_RESULTS, +}; + +static Array* search_index = NULL; // EntryArray, owns its entries +static Array* search_results = NULL; // borrowed pointers into search_index +static char search_query[SEARCH_QUERY_MAX+1] = ""; +static int search_cursor = 0; // insertion point in search_query, 0..strlen +static int search_query_all = 0; // query carried over from last time, selected: the next key typed replaces all of it +static int search_selected = 0; +static int search_start = 0; +static int search_focus = SEARCH_FOCUS_KEYBOARD; +static int search_key_row = 0; +static int search_key_col = 0; + +// Walks a system folder the way the browser does - same hidden-file rules, same +// map.txt aliases - but recursively, and treating a multi-disc folder as one game. +static void addSearchEntries(Array* entries, char* path, int depth) { + if (depth>SEARCH_MAX_DEPTH) return; + + DIR* dh = opendir(path); + if (!dh) return; + + Hash* map = NULL; + char map_path[256]; + snprintf(map_path, sizeof(map_path), "%s/map.txt", path); + if (exists(map_path)) { + FILE* file = fopen(map_path, "r"); + if (file) { + map = Hash_new(); + char line[256]; + while (fgets(line,256,file)!=NULL) { + normalizeNewline(line); + trimTrailingNewlines(line); + if (strlen(line)==0) continue; // skip empty lines + + char* tmp = strchr(line, '\t'); + if (tmp) { + tmp[0] = '\0'; + Hash_set(map, line, tmp+1); + } + } + fclose(file); + } + } + + struct dirent* dp; + char full_path[256]; + while ((dp = readdir(dh)) != NULL) { + if (entries->count>=SEARCH_INDEX_MAX) break; + if (hide(dp->d_name)) continue; + + snprintf(full_path, sizeof(full_path), "%s/%s", path, dp->d_name); + int is_dir = dp->d_type==DT_DIR || (dp->d_type==DT_UNKNOWN && isDirectory(full_path)); + if (is_dir && suffixMatch(".pak", dp->d_name)) continue; + + if (is_dir && !isGameDir(full_path)) { // just a folder, keep looking inside it + addSearchEntries(entries, full_path, depth+1); + continue; + } + + Entry* entry = Entry_new(full_path, is_dir ? ENTRY_DIR : ENTRY_ROM); + char* alias = map ? Hash_get(map, dp->d_name) : NULL; + if (alias) { + free(entry->name); + entry->name = strdup(alias); + } + if (hide(entry->name)) { // map.txt can hide an entry by renaming it + Entry_free(entry); + continue; + } + Array_push(entries, entry); + } + closedir(dh); + + if (map) Hash_free(map); +} + +// Built once per launch, on the first visit to the search screen. +static void buildSearchIndex(void) { + if (search_index) return; + + search_index = Array_new(); + + DIR* dh = opendir(ROMS_PATH); + if (dh) { + struct dirent* dp; + char full_path[256]; + while ((dp = readdir(dh)) != NULL) { + if (hide(dp->d_name)) continue; + if (!hasRoms(dp->d_name)) continue; // nothing to launch it with anyway + + snprintf(full_path, sizeof(full_path), "%s/%s", ROMS_PATH, dp->d_name); + addSearchEntries(search_index, full_path, 0); + } + closedir(dh); + } + + EntryArray_sort(search_index); + + // the same game routinely exists under two systems, so tag collisions with + // the emulator they belong to, exactly like a directory listing does + Entry* prior = NULL; + for (int i=0; icount; i++) { + Entry* entry = search_index->items[i]; + if (prior && exactMatch(prior->name, entry->name)) { + char unique[MAX_PATH]; + if (!prior->unique) { + getUniqueName(prior, unique); + prior->unique = strdup(unique); + } + getUniqueName(entry, unique); + entry->unique = strdup(unique); + } + prior = entry; + } +} + +static void updateSearchResults(void) { + if (!search_results) search_results = Array_new(); + search_results->count = 0; // entries belong to search_index, nothing to free + + if (search_index && search_query[0]) { + for (int i=0; icount; i++) { + Entry* entry = search_index->items[i]; + if (containsString(entry->name, search_query)) Array_push(search_results, entry); + } + } + + // the result set changed out from under it, so the old position means nothing + search_selected = 0; + search_start = 0; +} + +static int searchKeyboardHeight(void) { + return SCALE1(SEARCH_KEY_ROWS * SEARCH_KEY_H + (SEARCH_KEY_ROWS-1) * SEARCH_KEY_GAP); +} +static int searchListTop(void) { + return SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN); // below the query pill +} +// The keyboard gives up its space once the results have focus, so the list can +// use the whole screen for browsing and just a preview while typing. +static int searchListRows(void) { + int bottom = screen->h - SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN); + if (search_focus==SEARCH_FOCUS_KEYBOARD) bottom -= searchKeyboardHeight() + SCALE1(BUTTON_MARGIN); + + int rows = (bottom - searchListTop()) / SCALE1(PILL_SIZE); + return rows<1 ? 1 : rows; +} +static void searchScrollTo(int selected) { + int total = search_results ? search_results->count : 0; + if (total<=0) { + search_selected = 0; + search_start = 0; + return; + } + + int rows = searchListRows(); + search_selected = clamp(selected, 0, total-1); + if (search_selected=search_start+rows) search_start = search_selected - rows + 1; + if (search_start>total-rows) search_start = total - rows; + if (search_start<0) search_start = 0; +} + +static void openSearch(void) { + buildSearchIndex(); + // Reopening starts on the last query rather than a blank field, selected whole + // the way a desktop text field is: it is either the search you want to run + // again, or the text you are about to type over, and both are one button away. + search_query_all = search_query[0]!='\0'; + search_cursor = strlen(search_query); + search_focus = SEARCH_FOCUS_KEYBOARD; + search_key_row = 0; + search_key_col = 0; + updateSearchResults(); // so the carried-over query already has its results +} +static void searchClear(void) { + search_query[0] = '\0'; + search_cursor = 0; + search_query_all = 0; +} +static void searchAppend(char c) { + if (search_query_all) searchClear(); // typing replaces the selection, not appends to it + + int len = strlen(search_query); + if (len>=SEARCH_QUERY_MAX) return; + + memmove(search_query+search_cursor+1, search_query+search_cursor, len-search_cursor+1); // +1 for the terminator + search_query[search_cursor] = c; + search_cursor += 1; + updateSearchResults(); +} +static void searchBackspace(void) { + if (search_query_all) { // delete takes the whole selection, same as typing over it + searchClear(); + updateSearchResults(); + return; + } + if (search_cursor<=0) return; + + int len = strlen(search_query); + memmove(search_query+search_cursor-1, search_query+search_cursor, len-search_cursor+1); + search_cursor -= 1; + updateSearchResults(); +} +// Moving the cursor collapses the selection to the end it moved towards instead +// of clearing the query - the text is kept, it just stops being selected. +static void searchMoveCursor(int dir) { + int len = strlen(search_query); + if (search_query_all) { + search_query_all = 0; + search_cursor = dir<0 ? 0 : len; + return; + } + + search_cursor = clamp(search_cursor + dir, 0, len); +} +static Entry* searchSelectedEntry(void) { + if (!search_results || search_selected<0 || search_selected>=search_results->count) return NULL; + return search_results->items[search_selected]; +} +// So X still resumes a save state straight out of the results. +static void searchReadyResume(void) { + Entry* entry = searchSelectedEntry(); + if (entry) readyResume(entry); + else { + can_resume = 0; + has_preview = 0; + } +} +// The index is rebuilt per launch, but the query outlives the process so that +// searching, launching a game and coming back still offers the last search - +// which is the whole point, since launching a game is what exits nextui. +static void Search_init(void) { + if (!exists(SEARCH_QUERY_PATH)) return; + + getFile(SEARCH_QUERY_PATH, search_query, sizeof(search_query)); + for (char* c=search_query; *c; c++) { + if (*c<' ') { *c = '\0'; break; } // a stray newline would draw as a glyph in the pill + } + search_cursor = strlen(search_query); +} +static void Search_quit(void) { + putFile(SEARCH_QUERY_PATH, search_query); + + if (search_results) Array_free(search_results); + if (search_index) EntryArray_free(search_index); + search_results = NULL; + search_index = NULL; +} + +/////////////////////////////////////// + // Draws `text` vertically centered in `rect`, clipped to its width. static void blitRowText(const char* text, SDL_Rect* rect, SDL_Color color) { SDL_LockMutex(fontMutex); @@ -2633,6 +2917,156 @@ static void drawContextMenu(int ow) { GFX_blitButtonGroup((char*[]){ "B","CANCEL", "A","SELECT", NULL }, 1, screen, 1); } +static void drawSearch(int ow) { + int total = search_results ? search_results->count : 0; + int rows = searchListRows(); + + // query pill, with the match count parked at its right edge + { + char count[16] = ""; + int count_w = 0; + int count_h = 0; + if (search_query[0]) { + snprintf(count, sizeof(count), "%i", total); + GFX_sizeText(font.tiny, count, SCALE1(FONT_TINY), &count_w, &count_h); + count_w += SCALE1(BUTTON_MARGIN); + } + + int pill_width = screen->w - SCALE1(PADDING*2) - ow - SCALE1(BUTTON_MARGIN); + SDL_Rect pill_rect = { SCALE1(PADDING), SCALE1(PADDING), pill_width, SCALE1(PILL_SIZE) }; + GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill_rect); + + // A caret, so an empty query still reads as an input and not a label. It is + // spliced into the string rather than blitted separately so it lands on a + // glyph boundary without measuring the text twice. A selected query shows no + // caret - the selection is the cursor, and it covers every character. + char query[SEARCH_QUERY_MAX+2]; + if (search_query_all) snprintf(query, sizeof(query), "%s", search_query); + else snprintf(query, sizeof(query), "%.*s|%s", search_cursor, search_query, search_query+search_cursor); + + char display_query[SEARCH_QUERY_MAX+2]; + GFX_truncateText(font.large, query, display_query, pill_width - count_w, SCALE1(BUTTON_PADDING*2)); + + SDL_Rect text_rect = { + pill_rect.x + SCALE1(BUTTON_PADDING), + pill_rect.y, + pill_width - SCALE1(BUTTON_PADDING*2) - count_w, + pill_rect.h + }; + + // selection reuses the highlight the keyboard puts on the key under the + // cursor, so "this is about to be replaced" looks the same in both places + if (search_query_all) { + int query_w = 0; + int query_h = 0; + GFX_sizeText(font.large, display_query, SCALE1(FONT_LARGE), &query_w, &query_h); + if (query_w>text_rect.w) query_w = text_rect.w; + + SDL_Rect highlight_rect = { + text_rect.x - SCALE1(2), + pill_rect.y + (pill_rect.h - query_h - SCALE1(BUTTON_MARGIN)) / 2, + query_w + SCALE1(4), + query_h + SCALE1(BUTTON_MARGIN) + }; + + // the corners are drawn from the asset, so the rect cannot be smaller than + // the asset is - a one character query is narrow enough to reach that + SDL_Rect corner_rect; + GFX_assetRect(ASSET_STATE_BG, &corner_rect); + if (highlight_rect.ww, rows * SCALE1(PILL_SIZE) }; + GFX_blitMessage(font.large, search_query[0] ? "No matches" : "Type to search", screen, &message_rect); + } + else { + for (int i=search_start, j=0; iitems[i]; + char* entry_name = entry->unique ? entry->unique : entry->name; + trimSortingMeta(&entry_name); + + int available_width = screen->w - SCALE1(BUTTON_MARGIN*2); + char display_name[256]; + int text_width = GFX_truncateText(font.large, entry_name, display_name, available_width, SCALE1(BUTTON_PADDING*2)); + int max_width = MIN(available_width, text_width); + + SDL_Rect row_rect = { SCALE1(BUTTON_MARGIN), list_top + j * SCALE1(PILL_SIZE), max_width, SCALE1(PILL_SIZE) }; + int is_selected = (search_focus==SEARCH_FOCUS_RESULTS && i==search_selected); + if (is_selected) GFX_blitPillDark(ASSET_WHITE_PILL, screen, &row_rect); + + SDL_Rect text_rect = { + row_rect.x + SCALE1(BUTTON_PADDING), + row_rect.y, + max_width - SCALE1(BUTTON_PADDING*2), + row_rect.h + }; + blitRowText(display_name, &text_rect, uintToColour(is_selected ? THEME_COLOR5_255 : THEME_COLOR4_255)); + } + } + + // keyboard + if (search_focus==SEARCH_FOCUS_KEYBOARD) { + int key_width = SCALE1(SEARCH_KEY_W); + int key_height = SCALE1(SEARCH_KEY_H); + int key_pitch_x = SCALE1(SEARCH_KEY_W + SEARCH_KEY_GAP); + int key_pitch_y = SCALE1(SEARCH_KEY_H + SEARCH_KEY_GAP); + int keyboard_x = (screen->w - (SEARCH_KEY_COLS * key_pitch_x - SCALE1(SEARCH_KEY_GAP))) / 2; + int keyboard_y = screen->h - SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN) - searchKeyboardHeight(); + + for (int r=0; ritems[qm_col] : quickActions->items[qm_col]; - if(selected->type != ENTRY_DIP) { - currentScreen = SCREEN_GAMELIST; - total = top->entries->count; - // prevent restoring list state, game list screen currently isnt our nav origin - top->selected = 0; - top->start = 0; - top->end = top->start + MAIN_ROW_COUNT; - restore_depth = -1; - restore_relative = -1; - restore_selected = 0; - restore_start = 0; - restore_end = 0; + if (qm_row == 0 && exactMatch(selected->name, QUICK_SEARCH_NAME)) { + // a screen of its own rather than something to open or toggle + openSearch(); + currentScreen = SCREEN_SEARCH; + } + else { + if(selected->type != ENTRY_DIP) { + currentScreen = SCREEN_GAMELIST; + total = top->entries->count; + // prevent restoring list state, game list screen currently isnt our nav origin + top->selected = 0; + top->start = 0; + top->end = top->start + MAIN_ROW_COUNT; + restore_depth = -1; + restore_relative = -1; + restore_selected = 0; + restore_start = 0; + restore_end = 0; + } + Entry_open(selected); + if (top->entries->count > 0) readyResume(top->entries->items[top->selected]); } - Entry_open(selected); - if (top->entries->count > 0) readyResume(top->entries->items[top->selected]); dirty = 1; } else if (PAD_justPressed(BTN_RIGHT)) { @@ -2869,11 +3310,131 @@ int main (int argc, char *argv[]) { currentScreen = context_return_screen; total = top->entries->count; - if (total>0) readyResume(top->entries->items[top->selected]); + if (currentScreen==SCREEN_SEARCH) searchReadyResume(); + else if (total>0) readyResume(top->entries->items[top->selected]); folderbgchanged = 1; // the list underneath may have changed shape dirty = 1; } } + else if (currentScreen == SCREEN_SEARCH) { + int results = search_results ? search_results->count : 0; + + if (PAD_tappedMenu(now)) { + currentScreen = SCREEN_GAMELIST; + folderbgchanged = 1; // The background painting code is a clusterfuck, just force a repaint here + dirty = 1; + } + else if (search_focus==SEARCH_FOCUS_KEYBOARD) { + if (PAD_justPressed(BTN_B)) { + currentScreen = SCREEN_QUICKMENU; + folderbgchanged = 1; + dirty = 1; + } + else if (PAD_justPressed(BTN_START)) { + // run the query as it stands. the list is already filtered, so this is + // really "stop typing and go look at what it found" + search_query_all = 0; + if (results>0) { + search_focus = SEARCH_FOCUS_RESULTS; + searchScrollTo(search_selected); + searchReadyResume(); + } + dirty = 1; + } + else if (PAD_justPressed(BTN_A)) { + searchAppend(search_keys[search_key_row][search_key_col]); + dirty = 1; + } + else if (PAD_justRepeated(BTN_X)) { + searchBackspace(); + dirty = 1; + } + else if (PAD_justPressed(BTN_Y)) { + searchAppend(' '); + dirty = 1; + } + else if (PAD_justRepeated(BTN_L1) && !PAD_isPressed(BTN_R1)) { + searchMoveCursor(-1); + dirty = 1; + } + else if (PAD_justRepeated(BTN_R1) && !PAD_isPressed(BTN_L1)) { + searchMoveCursor(1); + dirty = 1; + } + else if (PAD_justRepeated(BTN_LEFT)) { + search_key_col -= 1; + if (search_key_col<0) search_key_col = SEARCH_KEY_COLS-1; + dirty = 1; + } + else if (PAD_justRepeated(BTN_RIGHT)) { + search_key_col += 1; + if (search_key_col>=SEARCH_KEY_COLS) search_key_col = 0; + dirty = 1; + } + else if (PAD_justRepeated(BTN_DOWN)) { + search_key_row += 1; + if (search_key_row>=SEARCH_KEY_ROWS) search_key_row = 0; + dirty = 1; + } + else if (PAD_justRepeated(BTN_UP)) { + // the results sit directly above the keyboard, so walking off + // the top row moves into them rather than wrapping around + if (search_key_row==0 && results>0) { + search_focus = SEARCH_FOCUS_RESULTS; + search_query_all = 0; // browsing the matches accepts the query, same as START + searchScrollTo(search_selected); + searchReadyResume(); + } + else { + search_key_row -= 1; + if (search_key_row<0) search_key_row = SEARCH_KEY_ROWS-1; + } + dirty = 1; + } + } + else { // SEARCH_FOCUS_RESULTS + if (PAD_justPressed(BTN_B)) { + search_focus = SEARCH_FOCUS_KEYBOARD; + searchScrollTo(search_selected); + dirty = 1; + } + else if (results>0 && can_resume && PAD_justReleased(BTN_RESUME) && searchSelectedEntry()) { + should_resume = 1; + Entry_open(searchSelectedEntry()); + dirty = 1; + } + else if (results>0 && PAD_justPressed(BTN_A) && searchSelectedEntry()) { + Entry_open(searchSelectedEntry()); + dirty = 1; + } + else if (results>0 && PAD_justPressed(BTN_Y)) { + if (openContextMenu(searchSelectedEntry(), SCREEN_SEARCH, 0)) + currentScreen = SCREEN_CONTEXTMENU; + dirty = 1; + } + else if (PAD_justRepeated(BTN_DOWN)) { + if (search_selected>=results-1) search_focus = SEARCH_FOCUS_KEYBOARD; + else searchScrollTo(search_selected+1); + searchReadyResume(); + dirty = 1; + } + else if (PAD_justRepeated(BTN_UP)) { + searchScrollTo(search_selected-1); + searchReadyResume(); + dirty = 1; + } + else if (PAD_justRepeated(BTN_LEFT)) { + searchScrollTo(search_selected - searchListRows()); + searchReadyResume(); + dirty = 1; + } + else if (PAD_justRepeated(BTN_RIGHT)) { + searchScrollTo(search_selected + searchListRows()); + searchReadyResume(); + dirty = 1; + } + } + } else if(currentScreen == SCREEN_GAMESWITCHER) { if (PAD_justPressed(BTN_B) || PAD_tappedSelect(now)) { currentScreen = SCREEN_GAMELIST; @@ -3258,15 +3819,16 @@ int main (int argc, char *argv[]) { GFX_flipHidden(); GFX_animateSurfaceOpacity(tmpOldScreen,0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 150:20,LAYER_BACKGROUND); } - else if(currentScreen == SCREEN_CONTEXTMENU) { - // keeps whatever background was already loaded and just drops the - // game art, so the panel underneath reads clearly - if(lastScreen != SCREEN_CONTEXTMENU) + else if(currentScreen == SCREEN_CONTEXTMENU || currentScreen == SCREEN_SEARCH) { + // both keep whatever background was already loaded and just drop + // the game art, so the panel/list underneath reads clearly + if(lastScreen != SCREEN_CONTEXTMENU && lastScreen != SCREEN_SEARCH) GFX_clearLayers(LAYER_THUMBNAIL); - drawContextMenu(ow); + if (currentScreen == SCREEN_CONTEXTMENU) drawContextMenu(ow); + else drawSearch(ow); - lastScreen = SCREEN_CONTEXTMENU; + lastScreen = currentScreen; } else if(currentScreen == SCREEN_GAMESWITCHER) { GFX_clearLayers(LAYER_ALL); @@ -3658,7 +4220,7 @@ int main (int argc, char *argv[]) { animationdirection = ANIM_NONE; } - if(lastScreen == SCREEN_QUICKMENU || lastScreen == SCREEN_CONTEXTMENU) { + if(lastScreen == SCREEN_QUICKMENU || lastScreen == SCREEN_CONTEXTMENU || lastScreen == SCREEN_SEARCH) { SDL_LockMutex(bgMutex); if(folderbgchanged) { if(folderbgbmp) @@ -3771,7 +4333,7 @@ int main (int argc, char *argv[]) { setAnimationDraw(0); } SDL_UnlockMutex(animMutex); - if (currentScreen != SCREEN_GAMESWITCHER && currentScreen != SCREEN_QUICKMENU && currentScreen != SCREEN_CONTEXTMENU) { + if (currentScreen != SCREEN_GAMESWITCHER && currentScreen != SCREEN_QUICKMENU && currentScreen != SCREEN_CONTEXTMENU && currentScreen != SCREEN_SEARCH) { if(is_scrolling && pillanimdone && currentAnimQueueSize < 1 && total>0) { int ow = GFX_blitHardwareGroup(screen, show_setting); Entry* entry = top->entries->items[top->selected];