diff options
| author | Adam Malczewski <[email protected]> | 2026-06-08 14:31:17 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-08 14:31:17 +0900 |
| commit | 0d154d253110dbe9e27790354c755f50fe47dd22 (patch) | |
| tree | 2cc9a7e6116e7c5deece492f406bb5b5ad38b0cb | |
| parent | 60c084ce1f9b1ff237b275ecbc89634468660077 (diff) | |
| download | study-player-0d154d253110dbe9e27790354c755f50fe47dd22.tar.gz study-player-0d154d253110dbe9e27790354c755f50fe47dd22.zip | |
ui: make smart play button and section nav configurable
- UILayout gains smartPlayY and secNavY fields
- Smart play HOLD button now uses layout.smartPlayY
- Section nav prev/next buttons use layout.secNavY
- Both are draggable in Layout editor tab
- config_load/config_save handle new fields
| -rw-r--r-- | src/config.c | 6 | ||||
| -rw-r--r-- | src/config.h | 2 | ||||
| -rw-r--r-- | src/layout_editor.c | 66 | ||||
| -rw-r--r-- | src/main.c | 55 |
4 files changed, 122 insertions, 7 deletions
diff --git a/src/config.c b/src/config.c index 1399de8..44951d6 100644 --- a/src/config.c +++ b/src/config.c @@ -23,6 +23,8 @@ static void set_defaults(UILayout *layout) layout->barX = (1920.0f - layout->barWidth) / 2.0f; layout->statusY = layout->barY + layout->barHeight + 30.0f; layout->btnY = layout->statusY + 120.0f + 55.0f; + layout->smartPlayY = 1080.0f - 80.0f - 120.0f; + layout->secNavY = layout->btnY + 55.0f + 80.0f + 30.0f; } static void chomp(char *line) @@ -99,6 +101,8 @@ static int parse_config(const char *path, UILayout *layout) else if (strcmp(key, "help_y") == 0) layout->helpY = f; else if (strcmp(key, "btn_y") == 0) layout->btnY = f; else if (strcmp(key, "btn_center_x") == 0) layout->btnCenterX = f; + else if (strcmp(key, "smart_play_y") == 0) layout->smartPlayY = f; + else if (strcmp(key, "sec_nav_y") == 0) layout->secNavY = f; loaded = 1; } @@ -141,6 +145,8 @@ int config_save(const char *exePath, const UILayout *layout) fprintf(fp, "help_y=%.2f\n", layout->helpY); fprintf(fp, "btn_y=%.2f\n", layout->btnY); fprintf(fp, "btn_center_x=%.2f\n", layout->btnCenterX); + fprintf(fp, "smart_play_y=%.2f\n", layout->smartPlayY); + fprintf(fp, "sec_nav_y=%.2f\n", layout->secNavY); fclose(fp); return 1; } diff --git a/src/config.h b/src/config.h index ce96a5b..c4300d1 100644 --- a/src/config.h +++ b/src/config.h @@ -11,6 +11,8 @@ typedef struct { float helpY; float btnY; float btnCenterX; + float smartPlayY; + float secNavY; } UILayout; int config_load(const char *exePath, UILayout *layout); diff --git a/src/layout_editor.c b/src/layout_editor.c index a728548..ef01640 100644 --- a/src/layout_editor.c +++ b/src/layout_editor.c @@ -51,10 +51,36 @@ void layout_editor_draw(const char *exePath, UILayout *layout) if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { dragIndex = -1; - /* Check element 4 first (Help), then 3, 2, 1, 0. + /* Check element 6 first, then 5, 4, 3, 2, 1, 0. Later checks overwrite earlier ones if they overlap — priority goes to elements drawn last / on top. */ + /* 6: Section nav buttons (two circles) */ + { + float secPrevX = (float)screenW / 2.0f - 65.0f; + float secNextX = (float)screenW / 2.0f + 65.0f; + float secBtnRadius = 35.0f; + float dx1 = mouse.x - secPrevX; + float dy1 = mouse.y - layout->secNavY; + float dx2 = mouse.x - secNextX; + float dy2 = mouse.y - layout->secNavY; + if ((dx1 * dx1 + dy1 * dy1 <= secBtnRadius * secBtnRadius) || + (dx2 * dx2 + dy2 * dy2 <= secBtnRadius * secBtnRadius)) { + dragIndex = 6; + dragOffsetY = mouse.y - layout->secNavY; + } + } + + /* 5: Smart play button (HOLD) */ + { + Rectangle r = { (float)screenW / 2.0f - 100.0f, + layout->smartPlayY, 200.0f, 80.0f }; + if (CheckCollisionPointRec(mouse, r)) { + dragIndex = 5; + dragOffsetY = mouse.y - layout->smartPlayY; + } + } + /* 4: Help */ { Rectangle r = { 40.0f, layout->helpY, HELP_W, HELP_H }; @@ -130,6 +156,12 @@ void layout_editor_draw(const char *exePath, UILayout *layout) case 4: /* Help */ layout->helpY = mouse.y - dragOffsetY; break; + case 5: /* Smart play */ + layout->smartPlayY = mouse.y - dragOffsetY; + break; + case 6: /* Section nav */ + layout->secNavY = mouse.y - dragOffsetY; + break; } } @@ -203,4 +235,36 @@ void layout_editor_draw(const char *exePath, UILayout *layout) Rectangle r = { 40.0f, layout->helpY, HELP_W, HELP_H }; draw_label("Help", r, f, borderColor); } + + /* --- 5: Smart play button --- */ + { + Color f = (dragIndex == 5) ? highlightColor : fillColor; + Rectangle r = { (float)screenW / 2.0f - 100.0f, + layout->smartPlayY, 200.0f, 80.0f }; + draw_label("HOLD", r, f, borderColor); + } + + /* --- 6: Section nav buttons --- */ + { + Color f = (dragIndex == 6) ? highlightColor : fillColor; + float secBtnRadius = 35.0f; + float secPrevX = (float)screenW / 2.0f - 65.0f; + float secNextX = (float)screenW / 2.0f + 65.0f; + float secCenterY = layout->secNavY; + + DrawCircle((int)secPrevX, (int)secCenterY, secBtnRadius, f); + DrawCircleLines((int)secPrevX, (int)secCenterY, secBtnRadius, borderColor); + DrawText("<", (int)secPrevX - MeasureText("<", labelFontSize) / 2, + (int)secCenterY - labelFontSize / 2, labelFontSize, labelColor); + + DrawCircle((int)secNextX, (int)secCenterY, secBtnRadius, f); + DrawCircleLines((int)secNextX, (int)secCenterY, secBtnRadius, borderColor); + DrawText(">", (int)secNextX - MeasureText(">", labelFontSize) / 2, + (int)secCenterY - labelFontSize / 2, labelFontSize, labelColor); + + const char *secLabel = "Sec"; + int secLabelW = MeasureText(secLabel, labelFontSize); + DrawText(secLabel, (int)((float)screenW / 2.0f - secLabelW / 2.0f), + (int)secCenterY - labelFontSize / 2, labelFontSize, labelColor); + } } @@ -58,6 +58,7 @@ static UILayout layout; static int activeTab = 0; static int prevTab = 0; +static bool smartPlayHeld = false; static char exeDir[512]; static int strcasecmp_ext(const char *a, const char *b) @@ -386,7 +387,6 @@ static void update_frame(void) if (portion > total) portion = total; char portionBuf[32]; snprintf(portionBuf, sizeof(portionBuf), "%d/%d", portion, total); - float portionY = layout.btnY + layout.btnRadius + 80; float portionSpacing = szSmall * 0.03f; Vector2 portionSize = MeasureTextEx(fontSmall, portionBuf, szSmall, portionSpacing); float portionX = (SCREEN_W - portionSize.x) / 2.0f; @@ -394,7 +394,7 @@ static void update_frame(void) float secBtnGap = 30.0f; float secPrevX = portionX - secBtnGap - secBtnRadius; float secNextX = portionX + portionSize.x + secBtnGap + secBtnRadius; - float secBtnY = portionY + szSmall / 2.0f; + float secBtnY = layout.secNavY; if (button_hit(secPrevX, secBtnY, secBtnRadius)) { @@ -419,6 +419,34 @@ static void update_frame(void) state.lastSilenceIdx = -1; } } + + /* --- Smart play hold button input --- */ + { + Rectangle smartBtn = { SCREEN_W/2.0f - 100.0f, layout.smartPlayY, 200.0f, 80.0f }; + Vector2 mouse = GetMousePosition(); + bool overBtn = (mouse.x >= smartBtn.x && mouse.x <= smartBtn.x + smartBtn.width && + mouse.y >= smartBtn.y && mouse.y <= smartBtn.y + smartBtn.height); + + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && overBtn) { + smartPlayHeld = true; + if (!state.playing) { + ResumeMusicStream(state.music); + state.playing = true; + } + } + + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && smartPlayHeld) { + /* Allow finger drift - stay held */ + } + + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { + smartPlayHeld = false; + } + + if (GetTouchPointCount() > 0 && smartPlayHeld) { + /* Touch fallback: keep held while touch points active */ + } + } } /* --- Keyboard input --- */ @@ -538,7 +566,7 @@ static void update_frame(void) int silIdx = find_silence_at(&state, pos); bool nowInSilence = (silIdx >= 0); - if (nowInSilence && !state.wasInSilence && !IsKeyDown(KEY_SPACE)) + if (nowInSilence && !state.wasInSilence && !IsKeyDown(KEY_SPACE) && !smartPlayHeld) { float target = segment_seek_target(&state, silIdx + 1); PauseMusicStream(state.music); @@ -547,7 +575,7 @@ static void update_frame(void) state.playing = false; state.skipAutoUpdate = 3; } - else if (!nowInSilence && state.wasInSilence && !IsKeyDown(KEY_SPACE)) + else if (!nowInSilence && state.wasInSilence && !IsKeyDown(KEY_SPACE) && !smartPlayHeld) { int portion = current_speaking_portion(&state, pos); float target = segment_seek_target(&state, portion); @@ -645,7 +673,7 @@ static void update_frame(void) if (portion > total) portion = total; char portionBuf[32]; snprintf(portionBuf, sizeof(portionBuf), "%d/%d", portion, total); - float portionY = layout.btnY + layout.btnRadius + 80; + float portionY = layout.secNavY - szSmall / 2.0f; float portionSpacing = szSmall * 0.03f; Vector2 portionSize = MeasureTextEx(fontSmall, portionBuf, szSmall, portionSpacing); float portionX = (SCREEN_W - portionSize.x) / 2.0f; @@ -656,7 +684,7 @@ static void update_frame(void) float secBtnGap = 30.0f; float secPrevX = portionX - secBtnGap - secBtnRadius; float secNextX = portionX + portionSize.x + secBtnGap + secBtnRadius; - float secBtnY_draw = portionY + szSmall / 2.0f; + float secBtnY_draw = layout.secNavY; float sd3 = mousePos.x - secPrevX, sd4 = mousePos.y - secBtnY_draw; bool hoverSecPrev = (sd3*sd3 + sd4*sd4) <= (secBtnRadius*secBtnRadius); @@ -670,6 +698,21 @@ static void update_frame(void) if (hoverSecNext) DrawCircle((int)secNextX, (int)secBtnY_draw, secBtnRadius, btnHoverColor); draw_seek_fwd_icon(secNextX, secBtnY_draw, 30, textColor); } + + /* --- Smart play hold button rendering --- */ + { + Rectangle smartBtn = { SCREEN_W/2.0f - 100.0f, layout.smartPlayY, 200.0f, 80.0f }; + Color btnFill = smartPlayHeld ? (Color){ 80, 30, 50, 220 } : (Color){ 50, 50, 70, 180 }; + Color btnBorder = smartPlayHeld ? accentColor : mutedColor; + Color btnTextColor = smartPlayHeld ? accentColor : textColor; + DrawRectangleRounded(smartBtn, 0.3f, 8, btnFill); + DrawRectangleRoundedLines(smartBtn, 0.3f, 8, btnBorder); + float btnSpacing = szSmall * 0.03f; + Vector2 btnSize = MeasureTextEx(fontSmall, "HOLD", szSmall, btnSpacing); + float tx = smartBtn.x + (smartBtn.width - btnSize.x) / 2.0f; + float ty = smartBtn.y + (smartBtn.height - szSmall) / 2.0f; + DrawTextEx(fontSmall, "HOLD", (Vector2){ tx, ty }, szSmall, btnSpacing, btnTextColor); + } } else { |
