Skip to content

feat: global rom search in the quick menu - #2

Open
lepht wants to merge 3 commits into
feat/favoritesfrom
feat/rom-search
Open

feat: global rom search in the quick menu#2
lepht wants to merge 3 commits into
feat/favoritesfrom
feat/rom-search

Conversation

@lepht

@lepht lepht commented Aug 8, 2026

Copy link
Copy Markdown
Owner

A global ROM search reachable from the quick menu, in the spirit of the stock TrimUI OS.

Stacked on #1. Base is feat/favorites, so this PR shows only its own commit. GitHub will retarget it to main automatically when #1 merges. The dependency is real, not just ordering: the Y-to-favorite binding in the results calls openContextMenu, and the indexer reuses isGameDir / isDirectory from that branch.

What this does

A Search entry in the quick menu opens an on-screen keyboard over a live result list. It indexes every ROM under Roms/ for which an emulator pak exists, honouring the same hidden-file and map.txt alias rules the browser uses, and treating a multi-disc folder as one game rather than a pile of discs. The index is built once, on the first visit.

The two halves hand focus back and forth: walking off the top of the keyboard moves into the results, which then take the keyboard's space so a long list can be browsed full-screen; walking off the bottom of the results gives it back. A launches, X resumes a save state, Y opens the same context menu the game lists use — so a game can be favorited straight out of a search.

Games that exist under two systems are tagged with the emulator they belong to, exactly like a directory listing does.

The query field behaves like a text field rather than an append-only buffer. L/R move a cursor through it a character at a time, typing inserts at the cursor, and X deletes the character before it — so a typo in the middle of a query doesn't mean backspacing over everything after it.

Reopening search starts on the previous query with the whole thing selected, the way a desktop text field does: START runs it again as-is, and typing replaces it outright. Moving into the results, by START or by walking up off the keyboard, accepts the query and drops the selection. The query lives in /tmp next to last.txt, so it survives launching a game — which is what exits nextui, and therefore the case that matters — but not a reboot.

Layout decision

At the Brick's 341×256 logical space there isn't room for a query field, a full result list, a keyboard, and the button hints at once. Rather than shrink everything, the result list and keyboard trade space based on focus: ~3 preview rows while typing, ~5 with the keyboard hidden. Row counts are computed from the actual screen height rather than hardcoded, so this adapts to other panels.

A button-hint group fits two hints at most, so the hints are contextual rather than crowded: a carried-over query spends them on the choice it presents (START SEARCH, A REPLACE), and the editing hints (Y SPACE, X DELETE, A TYPE) come back as soon as the selection is gone.

Testing

Built the desktop target and drove it under Xvfb against a fake SD card (~25 ROMs across 7 systems), verifying by screenshot:

  • live filtering as characters are typed, with a live match count in the query pill
  • focus handoff in both directions, and the row count changing with it
  • paging through a 19-result list and landing correctly on the last item
  • the empty states (Type to search vs No matches)
  • a multi-disc PS1 folder indexed as one result, not three
  • launching from results (correct emulator command queued, correct recents entry written)
  • opening the context menu on a result and favoriting from there

The query field was verified the same way, against a generated library of ~200 ROMs across 14 systems:

  • reopening restores the previous query, drawn selected, with its results already filtered
  • START runs the carried-over query and hands focus straight to the results
  • a keypress replaces the whole selection; L/R collapse it to either end instead, keeping the text
  • the cursor steps character by character, inserting mid-string and deleting the character before it
  • the query survives a game launch — written on exit, restored on the next run

Also exercised under AddressSanitizer with zero errors. Passes -fsyntax-only against both the tg5040 and tg5050 platform headers.

Not tested on hardware.

One note for anyone else driving the desktop build: PAD_poll drains the whole SDL event queue per call, so a synthetic instant keytap can have its press and release land in the same poll, which clears just_repeated while leaving just_pressed set — silently swallowing every d-pad and L/R input. Holding each key ~150ms avoids it. Real buttons are held across many polls, so this is a harness artifact only.

Performance note

The index is built synchronously on first open. On this test library it's imperceptible; on a card with thousands of ROMs it's a readdir/stat walk that could produce a brief hitch on that first visit. Capped at 8192 entries. If it turns out to matter on a real card, the walk is self-contained enough to move onto the existing loader thread.

Refs LoveRetro#110, LoveRetro#589

A new quick menu entry opens an on-screen keyboard over a live result list, in
the spirit of the stock TrimUI OS. It indexes every rom under Roms/ for which
an emulator pak exists, honouring the same hidden-file and map.txt alias rules
the browser uses, and treating a multi-disc folder as one game rather than as a
pile of discs. The index is built once, on the first visit.

The two halves of the screen hand focus back and forth: walking off the top of
the keyboard moves into the results, which then take the keyboard's space so a
long list can be browsed full-screen, and walking off the bottom of the results
gives it back. A launches, X resumes a save state, and Y opens the same context
menu the game lists use, so a game can be favorited straight out of a search.

Games that exist under two systems are tagged with the emulator they belong to,
exactly like a directory listing does.

Adds a Search icon to the shared res folder.

Refs LoveRetro#110, LoveRetro#589
claude added 2 commits August 12, 2026 04:39
Carries the main merge (and with it #26, #27, #28) up the stack.

Same single conflict as one level down, in the same two lines of nextui.c:
this branch had added SCREEN_SEARCH to the outer screen exclusion list on
top of SCREEN_CONTEXTMENU, while #27 added a total>0 guard to the inner
condition. Resolved as the union again - all four screen exclusions kept,
total>0 kept.

Verified after the merge: no assert(entry) remains, every read of
entries->items[top->selected] is guarded, nextui.c is syntax-clean against
the desktop platform headers, and make test passes.
Search opened on a blank field every time, so running a variant of the
search you just ran meant retyping the whole thing on an on-screen
keyboard. Reopening now starts on the previous query with the whole thing
selected, the way a desktop text field behaves: START runs it again
as-is, and typing replaces it outright. Moving into the results, by START
or by walking up off the keyboard, accepts the query and drops the
selection.

The query also outlives the process, in /tmp alongside last.txt. That is
the case that matters - launching a game is what exits nextui, so without
it the carried-over query would almost never be there when you came back.
Transient on purpose: a reboot starts clean.

The caret is a real cursor now rather than a fixed end-of-line marker.
L/R move it a character at a time, typing inserts there and delete takes
the character before it, so a typo in the middle of a query no longer
means backspacing over everything after it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVCcQ2X3F6F4pDb8ykQR33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants