From 201a8fae97308990bfca9367a34dc51779ce2214 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 14 Feb 2026 11:45:32 +0100 Subject: [PATCH] Split-screen layout: vertical controls left, full-height artwork right Redesign the 640x480 UI from horizontal bottom-bar controls to a split-screen layout that maximizes album art visibility for the child user. Buttons stack vertically in a 200px left panel; artwork fills the remaining right panel at up to 420x460. Volume removed from focus cycle in favor of dedicated keys (+/-, volume buttons, shoulder buttons). Navigation changed to UP/DOWN. Co-Authored-By: Claude Opus 4.6 --- docs/sdlamp2-fsd.md | 12 +++++- src/sdlamp2.c | 96 ++++++++++++++++++++++----------------------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/docs/sdlamp2-fsd.md b/docs/sdlamp2-fsd.md index ae09a0d..51a275c 100644 --- a/docs/sdlamp2-fsd.md +++ b/docs/sdlamp2-fsd.md @@ -7,7 +7,7 @@ | Version | 1.0 | | Status | Draft | | Created | 2026-02-10 | -| Updated | 2026-02-13 | +| Updated | 2026-02-14 | ## 1. Purpose @@ -43,6 +43,16 @@ This document specifies the functional requirements for an SDL2 based media play ## 6. Changelog +### 2026-02-14 — Split-screen layout with artwork focus + +- **Vertical left panel**: Transport controls (Prev, Rewind, Play/Stop, FF, Next) are stacked vertically in a 200px-wide left panel. Play/Stop is slightly larger (72x72) at the vertical center; other buttons are 56x56. +- **Full-height artwork**: Album art now fills the right panel (420x460 max bounds, centered at x=420, y=240), giving cassette covers nearly the full screen height instead of being constrained to the upper portion. +- **Vertical navigation**: D-pad UP/DOWN (and keyboard arrows) now navigate between buttons vertically instead of LEFT/RIGHT horizontally, matching the new stacked layout. +- **Dedicated volume controls**: Volume is no longer a focusable UI element. Adjusted via `+`/`-` keys, `SDLK_VOLUMEUP`/`SDLK_VOLUMEDOWN`, or controller shoulder buttons (L1/R1). +- **Sprite scaling quality**: Linear filtering (`SDL_HINT_RENDER_SCALE_QUALITY`) enabled for smoother downscaling of 200x200 sprite source to 56x56 button destinations. +- **No-art placeholder**: When a file has no embedded album art, a circle sprite from the spritesheet is rendered as a placeholder in the right panel. +- **Thin progress bar**: Progress bar moved to the bottom of the left panel (160x4px) as a subtle position indicator. + ### 2026-02-13 — Fix power button screen toggle regression - **Power button screen off stays off**: Fixed regression from fbd32d7 where short-pressing the power button to turn off the screen would instantly turn it back on. The generic wake logic (`any_activity`) was being triggered by power button events themselves. Moved `any_activity = True` below the power button handler's `continue` so power events are handled exclusively by the power button handler and don't trigger the wake path. diff --git a/src/sdlamp2.c b/src/sdlamp2.c index 36e1214..cdb0451 100644 --- a/src/sdlamp2.c +++ b/src/sdlamp2.c @@ -66,13 +66,12 @@ static int current_file_index = 0; /* --- Focus / navigation --- */ -#define FOCUS_VOLUME 0 -#define FOCUS_PREV 1 -#define FOCUS_REWIND 2 -#define FOCUS_PLAYSTOP 3 -#define FOCUS_FF 4 -#define FOCUS_NEXT 5 -#define FOCUS_COUNT 6 +#define FOCUS_PREV 0 +#define FOCUS_REWIND 1 +#define FOCUS_PLAYSTOP 2 +#define FOCUS_FF 3 +#define FOCUS_NEXT 4 +#define FOCUS_COUNT 5 static float volume = 0.5f; static int focus_index = FOCUS_PLAYSTOP; @@ -596,30 +595,28 @@ int main(int argc, char** argv) { audio_dir[sizeof(audio_dir) - 1] = '\0'; } - /* Volume slider (left of buttons) */ - const SDL_Rect volume_bg = {25, 390, 30, 80}; - - /* Button positions (bottom of window, centered) */ - const SDL_Rect prev_btn = {80, 390, 80, 80}; - const SDL_Rect rewind_btn = {180, 390, 80, 80}; - const SDL_Rect playstop_btn = {280, 390, 80, 80}; - const SDL_Rect ff_btn = {380, 390, 80, 80}; - const SDL_Rect next_btn = {480, 390, 80, 80}; + /* Left panel: buttons stacked vertically, centered at x=100 */ + const SDL_Rect prev_btn = {72, 34, 56, 56}; + const SDL_Rect rewind_btn = {72, 120, 56, 56}; + const SDL_Rect playstop_btn = {64, 206, 72, 72}; + const SDL_Rect ff_btn = {72, 308, 56, 56}; + const SDL_Rect next_btn = {72, 394, 56, 56}; /* Array of focusable rects indexed by FOCUS_* constants */ - const SDL_Rect* focus_rects[FOCUS_COUNT] = {&volume_bg, &prev_btn, &rewind_btn, - &playstop_btn, &ff_btn, &next_btn}; + const SDL_Rect* focus_rects[FOCUS_COUNT] = {&prev_btn, &rewind_btn, &playstop_btn, &ff_btn, + &next_btn}; /* Sprite sheet source rects */ const SDL_Rect rewind_sprite = {0, 0, 200, 200}; const SDL_Rect play_sprite = {220, 0, 200, 200}; const SDL_Rect ff_sprite = {440, 0, 200, 200}; const SDL_Rect stop_sprite = {0, 220, 200, 200}; - const SDL_Rect prev_sprite = {440, 220, 200, 200}; /* same circle as next */ + const SDL_Rect prev_sprite = {440, 220, 200, 200}; const SDL_Rect next_sprite = {440, 220, 200, 200}; + const SDL_Rect circle_sprite = {440, 220, 200, 200}; /* placeholder for no-art */ - /* Progress bar area */ - const SDL_Rect progress_bg = {20, 360, 600, 15}; + /* Progress bar — thin bar at bottom of left panel */ + const SDL_Rect progress_bg = {20, 466, 160, 4}; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) != 0) { panic_and_abort("SDL_Init failed!", SDL_GetError()); @@ -683,6 +680,8 @@ int main(int argc, char** argv) { } } + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); + SDL_Surface* controls_surface = IMG_Load("controls.png"); if (!controls_surface) { SDL_RWops* rw = SDL_RWFromConstMem(controls_png_data, controls_png_size); @@ -788,17 +787,20 @@ int main(int argc, char** argv) { case SDL_KEYDOWN: switch (e.key.keysym.sym) { - case SDLK_LEFT: + case SDLK_UP: focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT; break; - case SDLK_RIGHT: + case SDLK_DOWN: focus_index = (focus_index + 1) % FOCUS_COUNT; break; - case SDLK_UP: - if (focus_index == FOCUS_VOLUME) adjust_volume(0.05f); + case SDLK_EQUALS: + case SDLK_PLUS: + case SDLK_VOLUMEUP: + adjust_volume(0.05f); break; - case SDLK_DOWN: - if (focus_index == FOCUS_VOLUME) adjust_volume(-0.05f); + case SDLK_MINUS: + case SDLK_VOLUMEDOWN: + adjust_volume(-0.05f); break; case SDLK_RETURN: case SDLK_KP_ENTER: @@ -809,17 +811,17 @@ int main(int argc, char** argv) { case SDL_CONTROLLERBUTTONDOWN: switch (e.cbutton.button) { - case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + case SDL_CONTROLLER_BUTTON_DPAD_UP: focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT; break; - case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: focus_index = (focus_index + 1) % FOCUS_COUNT; break; - case SDL_CONTROLLER_BUTTON_DPAD_UP: - if (focus_index == FOCUS_VOLUME) adjust_volume(0.05f); + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + adjust_volume(-0.05f); break; - case SDL_CONTROLLER_BUTTON_DPAD_DOWN: - if (focus_index == FOCUS_VOLUME) adjust_volume(-0.05f); + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + adjust_volume(0.05f); break; case SDL_CONTROLLER_BUTTON_A: activate_focused_button(); @@ -829,14 +831,10 @@ int main(int argc, char** argv) { case SDL_JOYHATMOTION: { Uint8 hat = e.jhat.value; - if (hat & SDL_HAT_LEFT) { + if (hat & SDL_HAT_UP) { focus_index = (focus_index - 1 + FOCUS_COUNT) % FOCUS_COUNT; - } else if (hat & SDL_HAT_RIGHT) { - focus_index = (focus_index + 1) % FOCUS_COUNT; - } else if (hat & SDL_HAT_UP) { - if (focus_index == FOCUS_VOLUME) adjust_volume(0.05f); } else if (hat & SDL_HAT_DOWN) { - if (focus_index == FOCUS_VOLUME) adjust_volume(-0.05f); + focus_index = (focus_index + 1) % FOCUS_COUNT; } break; } @@ -919,16 +917,20 @@ int main(int argc, char** argv) { SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); SDL_RenderClear(renderer); - /* Album art (centered, aspect-preserving, in upper area) */ + /* Album art (right panel, centered, aspect-preserving) */ if (decoder.album_art && decoder.art_width > 0 && decoder.art_height > 0) { - int max_w = 600, max_h = 340; + int max_w = 420, max_h = 460; float scale_w = (float)max_w / decoder.art_width; float scale_h = (float)max_h / decoder.art_height; float scale = scale_w < scale_h ? scale_w : scale_h; int draw_w = (int)(decoder.art_width * scale); int draw_h = (int)(decoder.art_height * scale); - SDL_Rect art_rect = {(640 - draw_w) / 2, (350 - draw_h) / 2, draw_w, draw_h}; + SDL_Rect art_rect = {420 - draw_w / 2, 240 - draw_h / 2, draw_w, draw_h}; SDL_RenderCopy(renderer, decoder.album_art, NULL, &art_rect); + } else { + /* No-art placeholder: circle sprite scaled up in right panel */ + SDL_Rect placeholder = {420 - 100, 240 - 100, 200, 200}; + SDL_RenderCopy(renderer, controls_texture, &circle_sprite, &placeholder); } /* Progress bar */ @@ -946,15 +948,9 @@ int main(int argc, char** argv) { SDL_RenderFillRect(renderer, &fill); } - /* Volume slider */ + /* Separator line between left panel and right panel */ SDL_SetRenderDrawColor(renderer, 0xC0, 0xC0, 0xC0, 0xFF); - SDL_RenderFillRect(renderer, &volume_bg); - { - int fill_h = (int)(volume_bg.h * volume); - SDL_Rect vol_fill = {volume_bg.x, volume_bg.y + volume_bg.h - fill_h, volume_bg.w, fill_h}; - SDL_SetRenderDrawColor(renderer, 0x50, 0x50, 0x50, 0xFF); - SDL_RenderFillRect(renderer, &vol_fill); - } + SDL_RenderDrawLine(renderer, 200, 10, 200, 470); /* Buttons */ SDL_RenderCopy(renderer, controls_texture, &prev_sprite, &prev_btn);