summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-08 14:31:17 +0900
committerAdam Malczewski <[email protected]>2026-06-08 14:31:17 +0900
commit0d154d253110dbe9e27790354c755f50fe47dd22 (patch)
tree2cc9a7e6116e7c5deece492f406bb5b5ad38b0cb
parent60c084ce1f9b1ff237b275ecbc89634468660077 (diff)
downloadstudy-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.c6
-rw-r--r--src/config.h2
-rw-r--r--src/layout_editor.c66
-rw-r--r--src/main.c55
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);
+ }
}
diff --git a/src/main.c b/src/main.c
index f07b9a0..e041f49 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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
{