diff options
| author | Adam Malczewski <[email protected]> | 2026-06-08 13:47:55 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-08 13:47:55 +0900 |
| commit | 60c084ce1f9b1ff237b275ecbc89634468660077 (patch) | |
| tree | d7cbabd27c6352f25770c00a677397825bc1f29a | |
| parent | 2a31f14a71b207e910c932f1328ce119f7cd6981 (diff) | |
| download | study-player-60c084ce1f9b1ff237b275ecbc89634468660077.tar.gz study-player-60c084ce1f9b1ff237b275ecbc89634468660077.zip | |
ui: add tab bar, layout editor with drag-to-reposition
- GuiTabBar from raygui v5.0 for Player/Layout tab switching
- Layout tab: 5 draggable element placeholders (Title, Bar, Status,
Play button, Help) — press+hold+drag to reposition
- Auto-save config file on tab switch from Layout back to Player
- config_save() added to config module
- raygui include path added to Makefile, warnings suppressed via
#pragma for third-party header
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | src/config.c | 30 | ||||
| -rw-r--r-- | src/config.h | 1 | ||||
| -rw-r--r-- | src/layout_editor.c | 206 | ||||
| -rw-r--r-- | src/layout_editor.h | 5 | ||||
| -rw-r--r-- | src/main.c | 38 |
6 files changed, 280 insertions, 2 deletions
@@ -22,7 +22,7 @@ RAYLIB_SRCS := $(RAYLIB_SRC)/rcore.c $(RAYLIB_SRC)/rshapes.c $(RAYLIB_SRC)/rtext RAYLIB_OBJS := $(patsubst $(RAYLIB_SRC)/%.c,$(BUILD_DIR)/raylib/%.o,$(RAYLIB_SRCS)) # Common include paths -COMMON_INCS := -I$(BUILD_DIR) -I$(RAYLIB_SRC) -I$(RAYLIB_SRC)/external/glfw/include +COMMON_INCS := -I$(BUILD_DIR) -I$(RAYLIB_SRC) -I$(RAYLIB_SRC)/external/glfw/include -Ideps/raygui/src # --------------------------------------------------------------------------- # Linux native configuration (default target) diff --git a/src/config.c b/src/config.c index 1b3ecc2..1399de8 100644 --- a/src/config.c +++ b/src/config.c @@ -104,6 +104,10 @@ static int parse_config(const char *path, UILayout *layout) } fclose(fp); + + layout->barX = (1920.0f - layout->barWidth) / 2.0f; + layout->statusY = layout->barY + layout->barHeight + 30.0f; + return loaded; } @@ -114,5 +118,29 @@ int config_load(const char *exePath, UILayout *layout) char cfgPath[1024]; build_config_path(exePath, cfgPath, sizeof(cfgPath)); - return parse_config(cfgPath, layout); + int loaded = parse_config(cfgPath, layout); + layout->barX = (1920.0f - layout->barWidth) / 2.0f; + layout->statusY = layout->barY + layout->barHeight + 30.0f; + return loaded; +} + +int config_save(const char *exePath, const UILayout *layout) +{ + char cfgPath[1024]; + build_config_path(exePath, cfgPath, sizeof(cfgPath)); + + FILE *fp = fopen(cfgPath, "w"); + if (!fp) return 0; + + fprintf(fp, "# Study Player layout config\n"); + fprintf(fp, "title_y=%.2f\n", layout->titleY); + fprintf(fp, "bar_y=%.2f\n", layout->barY); + fprintf(fp, "bar_height=%.2f\n", layout->barHeight); + fprintf(fp, "bar_width=%.2f\n", layout->barWidth); + fprintf(fp, "btn_radius=%.2f\n", layout->btnRadius); + 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); + fclose(fp); + return 1; } diff --git a/src/config.h b/src/config.h index e2eb65e..ce96a5b 100644 --- a/src/config.h +++ b/src/config.h @@ -14,3 +14,4 @@ typedef struct { } UILayout; int config_load(const char *exePath, UILayout *layout); +int config_save(const char *exePath, const UILayout *layout); diff --git a/src/layout_editor.c b/src/layout_editor.c new file mode 100644 index 0000000..a728548 --- /dev/null +++ b/src/layout_editor.c @@ -0,0 +1,206 @@ +#include "layout_editor.h" +#include "raylib.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" +#pragma GCC diagnostic pop + +#define TITLE_W 400.0f +#define TITLE_H 70.0f +#define TIME_W 80.0f +#define PCT_W 60.0f +#define PCT_H 30.0f +#define STATUS_W 120.0f +#define STATUS_H 40.0f +#define HELP_W 600.0f +#define HELP_H 30.0f + +static int dragIndex = -1; +static float dragOffsetX = 0.0f; +static float dragOffsetY = 0.0f; + +void layout_editor_init(void) +{ + GuiLoadStyleDefault(); +} + +static Color fillColor = { 60, 60, 80, 100 }; +static Color borderColor = { 180, 180, 200, 200 }; +static Color highlightColor = { 233, 69, 96, 150 }; +static Color labelColor = { 234, 234, 234, 255 }; +static const int labelFontSize = 20; + +static void draw_label(const char *text, Rectangle r, Color fill, Color border) +{ + DrawRectangleRec(r, fill); + DrawRectangleLinesEx(r, 2.0f, border); + int textY = (int)(r.y + r.height / 2.0f - (float)labelFontSize / 2.0f); + DrawText(text, (int)r.x + 10, textY, labelFontSize, labelColor); +} + +void layout_editor_draw(const char *exePath, UILayout *layout) +{ + (void)exePath; + + int screenW = GetScreenWidth(); + Vector2 mouse = GetMousePosition(); + + /* --- Hit detection on press --- */ + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + dragIndex = -1; + + /* Check element 4 first (Help), then 3, 2, 1, 0. + Later checks overwrite earlier ones if they overlap — + priority goes to elements drawn last / on top. */ + + /* 4: Help */ + { + Rectangle r = { 40.0f, layout->helpY, HELP_W, HELP_H }; + if (CheckCollisionPointRec(mouse, r)) { + dragIndex = 4; + dragOffsetY = mouse.y - layout->helpY; + } + } + + /* 3: Play button (circle) */ + { + float dx = mouse.x - layout->btnCenterX; + float dy = mouse.y - layout->btnY; + if (dx * dx + dy * dy <= layout->btnRadius * layout->btnRadius) { + dragIndex = 3; + dragOffsetX = mouse.x - layout->btnCenterX; + dragOffsetY = mouse.y - layout->btnY; + } + } + + /* 2: Status */ + { + Rectangle r = { (float)screenW / 2.0f - STATUS_W / 2.0f, + layout->statusY, STATUS_W, STATUS_H }; + if (CheckCollisionPointRec(mouse, r)) { + dragIndex = 2; + dragOffsetY = mouse.y - layout->statusY; + } + } + + /* 1: Bar group (bar + time labels + percentage) */ + { + float gx = layout->barX - 90.0f; + float gw = layout->barWidth + 10.0f + 80.0f + 90.0f; + float gy = layout->barY - 40.0f; + float gh = layout->barHeight + 40.0f; + Rectangle group = { gx, gy, gw, gh }; + if (CheckCollisionPointRec(mouse, group)) { + dragIndex = 1; + dragOffsetX = mouse.x - layout->barX; + dragOffsetY = mouse.y - layout->barY; + } + } + + /* 0: Title */ + { + Rectangle r = { (float)screenW / 2.0f - TITLE_W / 2.0f, + layout->titleY, TITLE_W, TITLE_H }; + if (CheckCollisionPointRec(mouse, r)) { + dragIndex = 0; + dragOffsetY = mouse.y - layout->titleY; + } + } + } + + /* --- Drag update --- */ + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && dragIndex >= 0) { + switch (dragIndex) { + case 0: /* Title */ + layout->titleY = mouse.y - dragOffsetY; + break; + case 1: /* Bar group */ + layout->barX = mouse.x - dragOffsetX; + layout->barY = mouse.y - dragOffsetY; + break; + case 2: /* Status */ + layout->statusY = mouse.y - dragOffsetY; + break; + case 3: /* Play button */ + layout->btnCenterX = mouse.x - dragOffsetX; + layout->btnY = mouse.y - dragOffsetY; + break; + case 4: /* Help */ + layout->helpY = mouse.y - dragOffsetY; + break; + } + } + + /* --- Release --- */ + if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { + dragIndex = -1; + } + + Color barFill = (dragIndex == 1) ? highlightColor : fillColor; + + /* --- 0: Title --- */ + { + Color f = (dragIndex == 0) ? highlightColor : fillColor; + Rectangle r = { (float)screenW / 2.0f - TITLE_W / 2.0f, + layout->titleY, TITLE_W, TITLE_H }; + draw_label("Title", r, f, borderColor); + } + + /* --- 1: Bar group --- */ + /* Percentage above bar (centered on bar's X) */ + { + Rectangle r = { layout->barX + layout->barWidth / 2.0f - PCT_W / 2.0f, + layout->barY - 40.0f, PCT_W, PCT_H }; + draw_label("%", r, barFill, borderColor); + } + /* Elapsed time */ + { + Rectangle r = { layout->barX - 90.0f, layout->barY, + TIME_W, layout->barHeight }; + draw_label("Time", r, barFill, borderColor); + } + /* Bar */ + { + Rectangle r = { layout->barX, layout->barY, + layout->barWidth, layout->barHeight }; + draw_label("Bar", r, barFill, borderColor); + } + /* Remaining time */ + { + Rectangle r = { layout->barX + layout->barWidth + 10.0f, + layout->barY, TIME_W, layout->barHeight }; + draw_label("Time", r, barFill, borderColor); + } + + /* --- 2: Status --- */ + { + Color f = (dragIndex == 2) ? highlightColor : fillColor; + Rectangle r = { (float)screenW / 2.0f - STATUS_W / 2.0f, + layout->statusY, STATUS_W, STATUS_H }; + draw_label("Status", r, f, borderColor); + } + + /* --- 3: Play button --- */ + { + float cx = layout->btnCenterX; + float cy = layout->btnY; + float r = layout->btnRadius; + Color f = (dragIndex == 3) ? highlightColor : fillColor; + DrawCircle((int)cx, (int)cy, r, f); + DrawCircleLines((int)cx, (int)cy, r, borderColor); + float half = r * 0.5f; + 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, labelColor); + } + + /* --- 4: Help --- */ + { + Color f = (dragIndex == 4) ? highlightColor : fillColor; + Rectangle r = { 40.0f, layout->helpY, HELP_W, HELP_H }; + draw_label("Help", r, f, borderColor); + } +} diff --git a/src/layout_editor.h b/src/layout_editor.h new file mode 100644 index 0000000..a05cca7 --- /dev/null +++ b/src/layout_editor.h @@ -0,0 +1,5 @@ +#pragma once +#include "config.h" + +void layout_editor_init(void); +void layout_editor_draw(const char *exePath, UILayout *layout); @@ -4,6 +4,8 @@ #include <ctype.h> #include <stdio.h> #include "config.h" +#include "layout_editor.h" +#include "raygui.h" #include "font_data.h" #ifdef PLATFORM_LINUX @@ -54,6 +56,10 @@ static Color btnHoverColor; static UILayout layout; +static int activeTab = 0; +static int prevTab = 0; +static char exeDir[512]; + static int strcasecmp_ext(const char *a, const char *b) { while (*a && *b) { @@ -352,6 +358,8 @@ static void update_frame(void) } #endif + if (activeTab == 0) + { /* --- Button clicks --- */ if (state.loaded) { @@ -506,6 +514,7 @@ static void update_frame(void) } } } + } /* --- Music stream update --- */ if (state.loaded) @@ -555,10 +564,20 @@ static void update_frame(void) } } + /* --- Save layout on tab switch --- */ + if (prevTab == 1 && activeTab == 0) { + config_save(exeDir, &layout); + } + prevTab = activeTab; + /* --- Drawing --- */ BeginDrawing(); ClearBackground(bgColor); + char *tabNames[] = { "Player", "Layout" }; + GuiTabBar((Rectangle){ 0, 10, SCREEN_W, 32 }, tabNames, 2, &activeTab); + + if (activeTab == 0) { if (state.loaded) { draw_text_centered(font, state.filename, layout.titleY, szFont, mutedColor); @@ -693,6 +712,10 @@ static void update_frame(void) } } + } else { + layout_editor_draw(exeDir, &layout); + } + EndDrawing(); } @@ -744,6 +767,21 @@ int main(void) readlink("/proc/self/exe", exePath, sizeof(exePath) - 1); #endif config_load(exePath, &layout); + + { + const char *lastSlash = strrchr(exePath, '/'); + if (lastSlash) { + size_t len = (size_t)(lastSlash - exePath); + if (len >= sizeof(exeDir)) len = sizeof(exeDir) - 1; + memcpy(exeDir, exePath, len); + exeDir[len] = '\0'; + } else { + exeDir[0] = '.'; + exeDir[1] = '\0'; + } + } + + layout_editor_init(); } #ifdef PLATFORM_WEB |
