Fix patch browser keyboard navigation to cover directory rows

Arrow keys now navigate the full virtual row list (../, subdirs, patches)
rather than only patch rows. Landing on a patch auto-loads it as before;
landing on .. or a subdir highlights it and Enter confirms navigation.
Added patchVCursor to UIState to track keyboard focus independently of
the loaded patch. Mouse-click on a patch now also syncs the cursor so
subsequent arrow-key movement starts from the right position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Anachronaut
2026-07-07 22:32:46 -04:00
parent ff59c7c66a
commit e40645f280
4 changed files with 71 additions and 96 deletions

View File

@@ -137,6 +137,7 @@ void uiInit(UIState *ui, MidiState *midi, Camera2D *camera)
ui->patchFavCount = patchLoadFavs(ui->patchFavs, PATCH_MAX_FILES);
ui->patchCurrentDir[0] = '\0';
ui->patchSubDirCount = 0;
ui->patchVCursor = -1;
ui->controlRegistryCount = 0;
ui->pendingCount = 0;
}
@@ -827,6 +828,7 @@ static void patchRescan(UIState *ui)
ui->patchFiles, PATCH_MAX_FILES,
ui->patchSubDirs, PATCH_MAX_DIRS, &ui->patchSubDirCount);
ui->patchScrollOffset = 0;
ui->patchVCursor = -1;
}
void uiPatchMenu(UIState *ui, float x, float y, float width, Synth *s)
@@ -1042,32 +1044,69 @@ void uiPatchMenu(UIState *ui, float x, float y, float width, Synth *s)
ui->patchScrollOffset -= (int)GetMouseWheelMove();
}
// Arrow key navigation: move selection through patches only
// Arrow key navigation over all virtual rows: [..], subdirs, patches
if (ui->focusedTextInput == -1 && !ui->patchConfirmOverwrite && !ui->patchConfirmClear
&& filteredCount > 0) {
int curFi = -1;
for (int fi = 0; fi < filteredCount; fi++) {
if (strcmp(ui->patchFiles[filteredIdx[fi]], ui->patchCurrentName) == 0) {
curFi = fi;
break;
&& totalVirtual > 0) {
// Sync cursor from currently loaded patch if unset
if (ui->patchVCursor < 0 && ui->patchCurrentName[0]) {
for (int fi = 0; fi < filteredCount; fi++) {
if (strcmp(ui->patchFiles[filteredIdx[fi]], ui->patchCurrentName) == 0) {
ui->patchVCursor = fi + backRows + ui->patchSubDirCount;
break;
}
}
}
int nextFi = curFi;
if (IsKeyPressed(KEY_DOWN) && curFi < filteredCount - 1) nextFi = curFi + 1;
if (IsKeyPressed(KEY_UP) && curFi > 0) nextFi = curFi - 1;
if (nextFi != curFi) {
int i = filteredIdx[nextFi];
char path[512];
buildPatchPath(path, sizeof(path), ui->patchCurrentDir, ui->patchFiles[i]);
if (patchLoad(s, path)) {
snprintf(ui->patchCurrentName, PATCH_NAME_LEN, "%s", ui->patchFiles[i]);
snprintf(ui->patchSaveName, PATCH_NAME_LEN, "%s", ui->patchFiles[i]);
int next = ui->patchVCursor;
if (IsKeyPressed(KEY_DOWN))
next = (next < 0) ? 0 : (next < totalVirtual - 1 ? next + 1 : next);
if (IsKeyPressed(KEY_UP))
next = (next < 0) ? totalVirtual - 1 : (next > 0 ? next - 1 : next);
if (next != ui->patchVCursor) {
ui->patchVCursor = next;
// Auto-load when landing on a patch row
if (next >= backRows + ui->patchSubDirCount && filteredCount > 0) {
int fi = next - backRows - ui->patchSubDirCount;
int i = filteredIdx[fi];
char path[512];
buildPatchPath(path, sizeof(path), ui->patchCurrentDir, ui->patchFiles[i]);
if (patchLoad(s, path)) {
snprintf(ui->patchCurrentName, PATCH_NAME_LEN, "%s", ui->patchFiles[i]);
snprintf(ui->patchSaveName, PATCH_NAME_LEN, "%s", ui->patchFiles[i]);
}
}
int vRow = nextFi + backRows + ui->patchSubDirCount;
if (vRow < ui->patchScrollOffset)
ui->patchScrollOffset = vRow;
if (vRow >= ui->patchScrollOffset + MAX_VIS)
ui->patchScrollOffset = vRow - MAX_VIS + 1;
}
// Enter activates navigation rows (.. and subdirs)
if (IsKeyPressed(KEY_ENTER) && ui->patchVCursor >= 0) {
int vc = ui->patchVCursor;
if (!isRoot && vc == 0) {
char *lastSlash = strrchr(ui->patchCurrentDir, '/');
if (lastSlash) *lastSlash = '\0';
else ui->patchCurrentDir[0] = '\0';
ui->patchCurrentName[0] = '\0';
patchRescan(ui);
return;
} else if (vc >= backRows && vc < backRows + ui->patchSubDirCount) {
int d = vc - backRows;
if (ui->patchCurrentDir[0]) {
size_t curLen = strlen(ui->patchCurrentDir);
snprintf(ui->patchCurrentDir + curLen,
sizeof(ui->patchCurrentDir) - curLen,
"/%s", ui->patchSubDirs[d]);
} else {
snprintf(ui->patchCurrentDir, sizeof(ui->patchCurrentDir),
"%s", ui->patchSubDirs[d]);
}
ui->patchCurrentName[0] = '\0';
patchRescan(ui);
return;
}
}
// Scroll to keep cursor visible
if (ui->patchVCursor >= 0) {
if (ui->patchVCursor < ui->patchScrollOffset)
ui->patchScrollOffset = ui->patchVCursor;
if (ui->patchVCursor >= ui->patchScrollOffset + MAX_VIS)
ui->patchScrollOffset = ui->patchVCursor - MAX_VIS + 1;
}
}
@@ -1099,10 +1138,10 @@ void uiPatchMenu(UIState *ui, float x, float y, float width, Synth *s)
if (!isRoot && r == 0) {
// ".." — navigate to parent
DrawRectangle((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2),
(Color){20, 30, 50, 255});
DrawRectangleLines((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2),
(Color){80, 120, 180, 255});
Color dotBg = (ui->patchVCursor == r) ? (Color){40, 60, 100, 255} : (Color){20, 30, 50, 255};
Color dotEdge = (ui->patchVCursor == r) ? SKYBLUE : (Color){80, 120, 180, 255};
DrawRectangle((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2), dotBg);
DrawRectangleLines((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2), dotEdge);
DRAW_TEXT("..", (int)(x + padding + 4), (int)(contentY + 2), fontSize, SKYBLUE);
if (!ui->patchConfirmClear && IsMouseButtonPressed(MOUSE_LEFT_BUTTON) &&
mouse.x >= x + padding && mouse.x <= x + padding + btnW &&
@@ -1119,10 +1158,10 @@ void uiPatchMenu(UIState *ui, float x, float y, float width, Synth *s)
int d = r - backRows;
char label[PATCH_NAME_LEN + 4];
snprintf(label, sizeof(label), "> %s", ui->patchSubDirs[d]);
DrawRectangle((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2),
(Color){20, 30, 50, 255});
DrawRectangleLines((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2),
(Color){80, 120, 180, 255});
Color dirBg = (ui->patchVCursor == r) ? (Color){40, 60, 100, 255} : (Color){20, 30, 50, 255};
Color dirEdge = (ui->patchVCursor == r) ? SKYBLUE : (Color){80, 120, 180, 255};
DrawRectangle((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2), dirBg);
DrawRectangleLines((int)(x + padding), (int)contentY, (int)btnW, (int)(rowH - 2), dirEdge);
DRAW_TEXT(label, (int)(x + padding + 4), (int)(contentY + 2), fontSize, SKYBLUE);
if (!ui->patchConfirmClear && IsMouseButtonPressed(MOUSE_LEFT_BUTTON) &&
mouse.x >= x + padding && mouse.x <= x + padding + btnW &&
@@ -1190,6 +1229,7 @@ void uiPatchMenu(UIState *ui, float x, float y, float width, Synth *s)
if (patchLoad(s, path)) {
snprintf(ui->patchCurrentName, PATCH_NAME_LEN, "%s", ui->patchFiles[i]);
snprintf(ui->patchSaveName, PATCH_NAME_LEN, "%s", ui->patchFiles[i]);
ui->patchVCursor = r;
}
}
}