From 52afce7fa5271d424a53ffc600c498f5ca1273a0 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 18 Apr 2026 18:15:35 +0900 Subject: update to working state --- src/main.c | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 259 insertions(+), 15 deletions(-) diff --git a/src/main.c b/src/main.c index 0b9c795..ca774d3 100644 --- a/src/main.c +++ b/src/main.c @@ -1,12 +1,17 @@ #include "raylib.h" #include #include +#include + +#define SCREEN_W 1920 +#define SCREEN_H 1080 typedef struct { Music music; bool loaded; bool playing; float duration; + float currentTime; char filename[256]; } PlayerState; @@ -33,16 +38,127 @@ static void seek_to(PlayerState *s, float target) if (target < 0.0f) target = 0.0f; if (target > s->duration) target = s->duration; SeekMusicStream(s->music, target); + s->currentTime = target; +} + +static void format_time(float seconds, char *buf, int bufsize) +{ + int total = (int)seconds; + if (total < 0) total = 0; + int h = total / 3600; + int m = (total % 3600) / 60; + int s = total % 60; + if (h > 0) + snprintf(buf, bufsize, "%d:%02d:%02d", h, m, s); + else + snprintf(buf, bufsize, "%d:%02d", m, s); +} + +static void draw_text_centered(Font font, const char *text, float y, float fontSize, Color color) +{ + float spacing = fontSize * 0.08f; + Vector2 size = MeasureTextEx(font, text, fontSize, spacing); + float x = (SCREEN_W - size.x) / 2.0f; + DrawTextEx(font, text, (Vector2){ x, y }, fontSize, spacing, color); +} + +/* Draw a right-pointing triangle (play icon) centered at cx,cy */ +static void draw_play_icon(float cx, float cy, float size, Color color) +{ + float half = size / 2.0f; + Vector2 v1 = { cx - half * 0.7f, cy - half }; + Vector2 v2 = { cx - half * 0.7f, cy + half }; + Vector2 v3 = { cx + half * 0.8f, cy }; + DrawTriangle(v1, v2, v3, color); +} + +/* Draw two vertical bars (pause icon) centered at cx,cy */ +static void draw_pause_icon(float cx, float cy, float size, Color color) +{ + float half = size / 2.0f; + float barW = size * 0.25f; + float gap = size * 0.15f; + DrawRectangleRec((Rectangle){ cx - gap - barW, cy - half, barW, size }, color); + DrawRectangleRec((Rectangle){ cx + gap, cy - half, barW, size }, color); +} + +/* Draw a left-pointing triangle (seek back) centered at cx,cy */ +static void draw_seek_back_icon(float cx, float cy, float size, Color color) +{ + float half = size / 2.0f; + Vector2 v1 = { cx + half * 0.7f, cy - half }; + Vector2 v2 = { cx - half * 0.8f, cy }; + Vector2 v3 = { cx + half * 0.7f, cy + half }; + DrawTriangle(v1, v2, v3, color); +} + +/* Draw a right-pointing triangle (seek forward) centered at cx,cy */ +static void draw_seek_fwd_icon(float cx, float cy, float size, Color color) +{ + float half = size / 2.0f; + Vector2 v1 = { cx - half * 0.7f, cy - half }; + Vector2 v2 = { cx - half * 0.7f, cy + half }; + Vector2 v3 = { cx + half * 0.8f, cy }; + DrawTriangle(v1, v2, v3, color); +} + +static bool button_hit(float cx, float cy, float radius) +{ + if (!IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) return false; + Vector2 m = GetMousePosition(); + float dx = m.x - cx; + float dy = m.y - cy; + return (dx * dx + dy * dy) <= (radius * radius); } int main(void) { - InitWindow(1920, 1080, "Study Player"); + InitWindow(SCREEN_W, SCREEN_H, "Study Player"); InitAudioDevice(); SetTargetFPS(60); + const char *fontPath = "resources/Rotis Semi Serif Std 55 Regular.otf"; + bool fontLoaded = FileExists(fontPath); + Font fontSmall, font, fontMed, fontLarge; + if (fontLoaded) { + fontSmall = LoadFontEx(fontPath, 60, NULL, 0); /* time labels, help text */ + font = LoadFontEx(fontPath, 80, NULL, 0); /* filename, status */ + fontMed = LoadFontEx(fontPath, 100, NULL, 0); /* prompt */ + fontLarge = LoadFontEx(fontPath, 160, NULL, 0); /* title */ + } else { + fontSmall = GetFontDefault(); + font = GetFontDefault(); + fontMed = GetFontDefault(); + fontLarge = GetFontDefault(); + } + float szSmall = fontLoaded ? 60.0f : 40.0f; + float szFont = fontLoaded ? 80.0f : 40.0f; + float szMed = fontLoaded ? 100.0f : 60.0f; + float szLarge = fontLoaded ? 160.0f : 80.0f; + + Color bgColor = (Color){ 26, 26, 46, 255 }; /* #1a1a2e */ + Color textColor = (Color){ 234, 234, 234, 255 }; /* #eaeaea */ + Color accentColor = (Color){ 233, 69, 96, 255 }; /* #e94560 */ + Color mutedColor = (Color){ 140, 140, 160, 255 }; + Color barBgColor = (Color){ 60, 60, 60, 255 }; + Color btnHoverColor = (Color){ 255, 255, 255, 40 }; + PlayerState state = { 0 }; + /* Layout constants */ + const float titleY = 60.0f; + const float filenameY = 160.0f; + const float barY = 460.0f; + const float barHeight = 50.0f; + const float barWidth = SCREEN_W * 0.65f; + const float barX = (SCREEN_W - barWidth) / 2.0f; + const float statusY = barY + barHeight + 30.0f; + const float btnY = statusY + 120.0f + 55.0f; /* below status text + gap */ + const float btnRadius = 55.0f; + const float btnSpacing = 150.0f; + const float btnCenterX = SCREEN_W / 2.0f; + const float helpY = SCREEN_H - 50.0f; + while (!WindowShouldClose()) { /* --- 1.1 Drag & drop file loading --- */ @@ -56,7 +172,6 @@ int main(void) if (ext != NULL && strcasecmp_ext(ext, ".mp3") == 0) { - /* Unload previous */ if (state.loaded) { StopMusicStream(state.music); @@ -75,27 +190,60 @@ int main(void) state.filename[sizeof(state.filename) - 1] = '\0'; PlayMusicStream(state.music); + + char titleBuf[320]; + snprintf(titleBuf, sizeof(titleBuf), "Study Player - %s", state.filename); + SetWindowTitle(titleBuf); } } UnloadDroppedFiles(files); } - /* --- 1.4 Music stream update --- */ + /* --- Button clicks --- */ if (state.loaded) { - UpdateMusicStream(state.music); + /* Seek back button */ + if (button_hit(btnCenterX - btnSpacing, btnY, btnRadius)) + { + seek_to(&state, state.currentTime - 5.0f); + } + + /* Play/pause button */ + if (button_hit(btnCenterX, btnY, btnRadius)) + { + if (state.playing) + { + PauseMusicStream(state.music); + float rewind = state.currentTime - 1.0f; + if (rewind < 0.0f) rewind = 0.0f; + SeekMusicStream(state.music, rewind); + state.currentTime = rewind; + state.playing = false; + } + else + { + ResumeMusicStream(state.music); + state.playing = true; + } + } + + /* Seek forward button */ + if (button_hit(btnCenterX + btnSpacing, btnY, btnRadius)) + { + seek_to(&state, state.currentTime + 5.0f); + } } - /* --- 1.2 Play/pause with rewind --- */ + /* --- 1.2 Play/pause with keyboard --- */ if (state.loaded && IsKeyPressed(KEY_SPACE)) { if (state.playing) { - float pos = GetMusicTimePlayed(state.music); PauseMusicStream(state.music); - float rewind = pos - 1.0f; + float rewind = state.currentTime - 1.0f; if (rewind < 0.0f) rewind = 0.0f; SeekMusicStream(state.music, rewind); + state.currentTime = rewind; state.playing = false; } else @@ -105,32 +253,122 @@ int main(void) } } + /* --- 2.3 Click-to-seek on progress bar --- */ + if (state.loaded && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) + { + Vector2 mouse = GetMousePosition(); + if (mouse.x >= barX && mouse.x <= barX + barWidth && + mouse.y >= barY && mouse.y <= barY + barHeight) + { + float target = ((mouse.x - barX) / barWidth) * state.duration; + seek_to(&state, target); + } + } + /* --- 1.3 Arrow key seeking --- */ if (state.loaded && IsKeyPressed(KEY_LEFT)) { - float pos = GetMusicTimePlayed(state.music); - seek_to(&state, pos - 5.0f); + seek_to(&state, state.currentTime - 5.0f); } if (state.loaded && IsKeyPressed(KEY_RIGHT)) { - float pos = GetMusicTimePlayed(state.music); - seek_to(&state, pos + 5.0f); + seek_to(&state, state.currentTime + 5.0f); + } + + /* --- 1.4 Music stream update (after input so state is fresh) --- */ + if (state.loaded) + { + UpdateMusicStream(state.music); + if (state.playing) + state.currentTime = GetMusicTimePlayed(state.music); } /* --- Drawing --- */ BeginDrawing(); - ClearBackground((Color){ 26, 26, 46, 255 }); + ClearBackground(bgColor); + + /* 3.1 Title */ + draw_text_centered(fontLarge, "Study Player", titleY, szLarge, textColor); if (state.loaded) { -DrawText(state.filename, 40, 40, 40, RAYWHITE); -DrawText(state.playing ? "Playing" : "Paused", 40, 90, 30, GRAY); + /* 3.2 Filename */ + draw_text_centered(font, state.filename, filenameY, szFont, mutedColor); + + /* --- 2.1 Progress bar --- */ + float currentTime = state.currentTime; + float progress = (state.duration > 0.0f) ? currentTime / state.duration : 0.0f; + if (progress > 1.0f) progress = 1.0f; + + Rectangle barBg = { barX, barY, barWidth, barHeight }; + Rectangle barFill = { barX, barY, barWidth * progress, barHeight }; + DrawRectangleRounded(barBg, 0.4f, 8, barBgColor); + if (progress > 0.001f) + DrawRectangleRounded(barFill, 0.4f, 8, accentColor); + + /* --- 2.2 Time labels --- */ + char timeBuf[16]; + format_time(currentTime, timeBuf, sizeof(timeBuf)); + float timeFontSize = szSmall; + float timeSpacing = timeFontSize * 0.08f; + Vector2 leftSize = MeasureTextEx(fontSmall, timeBuf, timeFontSize, timeSpacing); + DrawTextEx(fontSmall, timeBuf, (Vector2){ barX - leftSize.x - 20, barY + (barHeight - timeFontSize) / 2.0f }, timeFontSize, timeSpacing, textColor); + + char rightBuf[32]; + float remaining = state.duration - currentTime; + if (remaining < 0.0f) remaining = 0.0f; + format_time(remaining, timeBuf, sizeof(timeBuf)); + int pct = (int)(progress * 100.0f); + snprintf(rightBuf, sizeof(rightBuf), "%d%%", pct); + Vector2 rightTimeSize = MeasureTextEx(fontSmall, timeBuf, timeFontSize, timeSpacing); + Vector2 rightPctSize = MeasureTextEx(fontSmall, rightBuf, timeFontSize, timeSpacing); + float rightMaxW = rightTimeSize.x > rightPctSize.x ? rightTimeSize.x : rightPctSize.x; + float rightX = barX + barWidth + 20; + DrawTextEx(fontSmall, timeBuf, (Vector2){ rightX + (rightMaxW - rightTimeSize.x) / 2, barY - 2 }, timeFontSize, timeSpacing, textColor); + DrawTextEx(fontSmall, rightBuf, (Vector2){ rightX + (rightMaxW - rightPctSize.x) / 2, barY + 34 }, timeFontSize, timeSpacing, textColor); + + /* 3.3 Playback status */ + draw_text_centered(font, state.playing ? "PLAYING" : "PAUSED", statusY, szFont, accentColor); + + /* --- Buttons --- */ + Vector2 mousePos = GetMousePosition(); + + /* Seek back button */ + float sbx = btnCenterX - btnSpacing; + float sdx1 = mousePos.x - sbx, sdy1 = mousePos.y - btnY; + bool hoverBack = (sdx1*sdx1 + sdy1*sdy1) <= (btnRadius*btnRadius); + DrawCircle((int)sbx, (int)btnY, btnRadius, (Color){ 50, 50, 70, 255 }); + if (hoverBack) DrawCircle((int)sbx, (int)btnY, btnRadius, btnHoverColor); + draw_seek_back_icon(sbx, btnY, 50, textColor); + + /* Play/pause button */ + float ppx = btnCenterX; + float pdx = mousePos.x - ppx, pdy = mousePos.y - btnY; + bool hoverPP = (pdx*pdx + pdy*pdy) <= ((btnRadius+5)*(btnRadius+5)); + DrawCircle((int)ppx, (int)btnY, btnRadius + 8, accentColor); + if (hoverPP) DrawCircle((int)ppx, (int)btnY, btnRadius + 8, btnHoverColor); + if (state.playing) + draw_pause_icon(ppx, btnY, 50, textColor); + else + draw_play_icon(ppx, btnY, 50, textColor); + + /* Seek forward button */ + float sfx = btnCenterX + btnSpacing; + float sdx2 = mousePos.x - sfx, sdy2 = mousePos.y - btnY; + bool hoverFwd = (sdx2*sdx2 + sdy2*sdy2) <= (btnRadius*btnRadius); + DrawCircle((int)sfx, (int)btnY, btnRadius, (Color){ 50, 50, 70, 255 }); + if (hoverFwd) DrawCircle((int)sfx, (int)btnY, btnRadius, btnHoverColor); + draw_seek_fwd_icon(sfx, btnY, 50, textColor); } else { -DrawText("Drop an MP3 file here to play", 540, 340, 40, RAYWHITE); + /* 3.2 No file prompt — vertically centered */ + draw_text_centered(fontMed, "Drag an MP3 file here", SCREEN_H / 2.0f - 20, szMed, mutedColor); } + /* 3.4 Help text */ + draw_text_centered(fontSmall, "Space: play/pause Left/Right: seek 5s Click bar: seek", helpY, szSmall, mutedColor); + EndDrawing(); } @@ -140,6 +378,12 @@ DrawText("Drop an MP3 file here to play", 540, 340, 40, RAYWHITE); UnloadMusicStream(state.music); } + if (fontLoaded) { + UnloadFont(fontSmall); + UnloadFont(font); + UnloadFont(fontMed); + UnloadFont(fontLarge); + } CloseAudioDevice(); CloseWindow(); -- cgit v1.2.3