From 888a7a9962814ef0d1a2f0fd0d5b241dad511ed8 Mon Sep 17 00:00:00 2001 From: Edgaru089 Date: Wed, 24 Apr 2024 22:23:01 +0800 Subject: [PATCH] Beautiful GUIs --- app.c | 20 ++++------ app_debug.c | 12 +++++- app_file.c | 2 + types.h | 4 +- ui.c | 26 ++++++------- ui.h | 2 +- ui_build.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++- ui_button.c | 7 +++- ui_render.cpp | 6 ++- 9 files changed, 148 insertions(+), 33 deletions(-) diff --git a/app.c b/app.c index dd0b94a..99d9a32 100644 --- a/app.c +++ b/app.c @@ -16,6 +16,11 @@ #include +#ifndef RGB +#define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) +#endif + + App *app_NewApp() { render_LoadBundle("bundles.txt"); @@ -30,26 +35,15 @@ App *app_NewApp() { app->time = gametime_NewSystem(app); app->ui = ui_NewSystem(app); ui_RebuildUI(app->ui); - ui_PushState(app->ui, ui_Running); + ui_PushState(app->ui, ui_TitleMenu); app->switch_level = NULL; app->timescale = 1.0; - app->clear_color = 0; + app->clear_color = RGB(40, 40, 40); app->wantQuit = false; app->paused = false; - Entity *player = entity_Create(app->entity, "player"); - ADD_COMPONENT(player, player); - ADD_COMPONENT(player, position); - player->position->position = vec2(500, 500); - player->position->velocity = vec2(0, 0); - ADD_COMPONENT(player, hitbox); - player->hitbox->box.lefttop = vec2(-20, -80); - - app_QueueLoadLevel(app, "intro.txt"); - _app_SwitchLevel(app); - return app; } diff --git a/app_debug.c b/app_debug.c index 0605011..ae631cc 100644 --- a/app_debug.c +++ b/app_debug.c @@ -1,6 +1,7 @@ #include "app.h" #include "types.h" +#include "ui.h" #include "util/tree.h" #include "util/vector.h" @@ -46,9 +47,18 @@ void app_DebugText(App *app, vector_Vector *vec_string) { PUSH_STRING(buf); snprintf( buf, sizeof(buf) - 1, - "Particle count[0]: %d\n", tree_Count(app->particle->parts[0])); + "Particle count[0]: %d\n\nUI Stack:\n", tree_Count(app->particle->parts[0])); PUSH_STRING(buf); + for (int i = 0; i < vector_Size(app->ui->state); i++) { + ui_State *s = vector_At(app->ui->state, i); + snprintf( + buf, sizeof(buf) - 1, + " %s\n", ui_StateTitle[*s]); + PUSH_STRING(buf); + } + + char zero = '\0'; vector_Push(vec_string, &zero); } diff --git a/app_file.c b/app_file.c index c6e985c..7961f49 100644 --- a/app_file.c +++ b/app_file.c @@ -227,6 +227,8 @@ void _app_SwitchLevel(App *app) { FILE *f = fopen(app->switch_level, "r"); if (!f) { WARN("failed to open file\"%s\"", app->switch_level); + free(app->switch_level); + app->switch_level = NULL; return; } diff --git a/types.h b/types.h index 0008650..865d56f 100644 --- a/types.h +++ b/types.h @@ -11,8 +11,8 @@ extern "C" { #endif -#define SCREEN_WIDTH 1536 -#define SCREEN_HEIGHT 864 +#define SCREEN_WIDTH 1440 +#define SCREEN_HEIGHT 810 #define WARN(fmt, ...) fprintf(stderr, "[WARN][%s] " fmt "\n", __func__, ##__VA_ARGS__) #define INFO(fmt, ...) fprintf(stderr, "[%s] " fmt "\n", __func__, ##__VA_ARGS__) diff --git a/ui.c b/ui.c index 0a319dc..66104b3 100644 --- a/ui.c +++ b/ui.c @@ -90,20 +90,20 @@ void ui_Advance(System_UI *sys, Duration deltaTime) { sys->bg.lefttop = lt; sys->bg.size = vec2_Minus(rb, lt); - for (int i = 0; i < ui_StateCount; i++) - for (int j = 0; j < vector_Size(sys->parts[i]); j++) { - ui_Part *part = (ui_Part *)vector_At(sys->parts[i], j); - // Hover check - if (box2_Contains(part->box, input_MousePosition(sys->super->input))) { - part->hovered = true; - part->progress = dmin(part->progress + UI_BUTTON_HOVER_SPEED * duration_Seconds(deltaTime), 1.0); - } else { - part->hovered = false; - part->progress = dmax(part->progress - UI_BUTTON_HOVER_SPEED * duration_Seconds(deltaTime), 0.0); - } - - part->update(sys, part, part->user, deltaTime); + ui_State state = ui_CurrentState(sys); + for (int i = 0; i < vector_Size(sys->parts[state]); i++) { + ui_Part *part = (ui_Part *)vector_At(sys->parts[state], i); + // Hover check + if (box2_Contains(part->box, input_MousePosition(sys->super->input))) { + part->hovered = true; + part->progress = dmin(part->progress + UI_BUTTON_HOVER_SPEED * duration_Seconds(deltaTime), 1.0); + } else { + part->hovered = false; + part->progress = dmax(part->progress - UI_BUTTON_HOVER_SPEED * duration_Seconds(deltaTime), 0.0); } + + part->update(sys, part, part->user, deltaTime); + } } diff --git a/ui.h b/ui.h index be6b761..03b14a9 100644 --- a/ui.h +++ b/ui.h @@ -63,7 +63,7 @@ void ui_PopState(System_UI *sys); ui_State ui_CurrentState(System_UI *sys); -#define UI_PADDING (20) +#define UI_PADDING (16) #define UI_BACKGROUND (0) #define UI_BACKGROUND_FADE_MOVE_SPEED (20.0) diff --git a/ui_build.c b/ui_build.c index 62574fd..74b258e 100644 --- a/ui_build.c +++ b/ui_build.c @@ -2,12 +2,46 @@ #include "ui.h" #include "app.h" #include "util/vector.h" +#include + + +#ifndef RGB +#define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16)) +#endif static void _ui_Action_ResumeGame(System_UI *sys, ui_Part *part, uintptr_t data) { ui_Action_PopState(sys, part, data); sys->super->paused = false; } +static void _ui_Action_ReturnToTitle(System_UI *sys, ui_Part *part, uintptr_t data) { + while (vector_Size(sys->state) > 0 && ui_CurrentState(sys) != ui_TitleMenu) { + INFO("popping \"%s\"", ui_StateTitle[ui_CurrentState(sys)]); + ui_Action_PopState(sys, part, data); + } + sys->super->paused = false; + if (vector_Size(sys->state) == 0) + WARN("new state stack is now empty, ohno"); + + sys->super->clear_color = RGB(40, 40, 40); + // Clear the app + // see app_file.c:233 + entity_Clear(sys->super->entity); + particle_Clear(sys->super->particle); + sys->super->camera->target = NULL; +} +static void _ui_Action_SelectLevel(System_UI *sys, ui_Part *part, uintptr_t data) { + const char *level = (const char *)data; + app_QueueLoadLevel(sys->super, level); + ui_Action_PushState(sys, part, (uintptr_t)ui_Running); + sys->super->paused = false; +} +static void _ui_Action_StartFromBeginning(System_UI *sys, ui_Part *part, uintptr_t data) { + _ui_Action_SelectLevel(sys, part, (uintptr_t) "intro.txt"); +} +static void _ui_Action_Quit(System_UI *sys, ui_Part *part, uintptr_t data) { + sys->super->wantQuit = true; +} #define PUSH_UI(part) \ @@ -18,12 +52,78 @@ void ui_RebuildUI(System_UI *sys) { vector_Vector *p; ui_Part _p0; + // Title Menu + p = sys->parts[ui_TitleMenu]; + vector_Clear(p); + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 250), vec2(500, 50)), + "Start from Beginning", + &_ui_Action_StartFromBeginning, 0)) + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 250 + (50 + UI_PADDING)), vec2(500, 50)), + "Select Level", + &ui_Action_PushState, ui_SceneSelect)) + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0 - (500 - UI_PADDING) / 4.0 - UI_PADDING / 2.0, 250 + 2 * (50 + UI_PADDING)), vec2((500 - UI_PADDING) / 2.0, 40)), + "Options", + &ui_Action_PushState, (uintptr_t)ui_Options)) + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0 + (500 - UI_PADDING) / 4.0 + UI_PADDING / 2.0, 250 + 2 * (50 + UI_PADDING)), vec2((500 - UI_PADDING) / 2.0, 40)), + "Quit", + &_ui_Action_Quit, 0)) + + + // Select Level + p = sys->parts[ui_SceneSelect]; + vector_Clear(p); + PUSH_UI(ui_Button_NewLeftAligned( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 280), vec2(400, 40)), + "Introduction", + &_ui_Action_SelectLevel, (uintptr_t) "intro.txt")); + PUSH_UI(ui_Button_NewLeftAligned( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 280 + (40 + UI_PADDING / 2.0)), vec2(400, 40)), + "Scene 1", + &_ui_Action_SelectLevel, (uintptr_t) "level1.txt")); + PUSH_UI(ui_Button_NewLeftAligned( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 280 + 2 * (40 + UI_PADDING / 2.0)), vec2(400, 40)), + "Scene 2", + &_ui_Action_SelectLevel, (uintptr_t) "level2.txt")); + PUSH_UI(ui_Button_NewLeftAligned( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 280 + 3 * (40 + UI_PADDING / 2.0)), vec2(400, 40)), + "Scene 3", + &_ui_Action_SelectLevel, (uintptr_t) "level3.txt")); + PUSH_UI(ui_Button_NewLeftAligned( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0 + 100, 280 + 3 * (40 + UI_PADDING / 2.0) + (40 + UI_PADDING)), vec2(200, 40)), + "Return", + &ui_Action_PopState, 0)); + // Paused p = sys->parts[ui_Paused]; vector_Clear(p); PUSH_UI(ui_Button_New( - box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 300), vec2(400, 50)), + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 300), vec2(400, 40)), "Resume Game", &_ui_Action_ResumeGame, 0)) + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 300 + (40 + UI_PADDING)), vec2(400, 40)), + "Reselect Level", + &ui_Action_PushState, (uintptr_t)ui_SceneSelect)) + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 300 + 2 * (40 + UI_PADDING)), vec2(400, 40)), + "Options", + &ui_Action_PushState, (uintptr_t)ui_Options)) + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 300 + 3 * (40 + UI_PADDING) + UI_PADDING), vec2(400, 40)), + "Return to Title", + &_ui_Action_ReturnToTitle, 0)) + + + // Options + p = sys->parts[ui_Options]; + vector_Clear(p); + PUSH_UI(ui_Button_New( + box2_FromCenter(vec2(SCREEN_WIDTH / 2.0, 300), vec2(600, 50)), + "Return", + &ui_Action_PopState, 0)) } diff --git a/ui_button.c b/ui_button.c index 63153c5..bbd51d7 100644 --- a/ui_button.c +++ b/ui_button.c @@ -42,4 +42,9 @@ ui_Part ui_Button_New(Box2 box, const char *label, ui_ActionCallback callback, u .free = &_ui_Button_Free}; return part; } -ui_Part ui_Button_NewLeftAligned(Box2 box, const char *label, ui_ActionCallback callback, uintptr_t data); +ui_Part ui_Button_NewLeftAligned(Box2 box, const char *label, ui_ActionCallback callback, uintptr_t data) { + ui_Part part = ui_Button_New(box, label, callback, data); + + ((ui_Button *)part.user)->left_aligned = true; + return part; +} diff --git a/ui_render.cpp b/ui_render.cpp index 38e38c1..39add37 100644 --- a/ui_render.cpp +++ b/ui_render.cpp @@ -66,10 +66,14 @@ extern "C" void _ui_Button_Draw(System_UI *sys, ui_Part *part, void *user) { setfillcolor(color_final); settextcolor(RGB(255, 255, 255)); + Box2 padded_box = part->box; + padded_box.lefttop.x += UI_PADDING; + padded_box.size.x -= 2 * UI_PADDING; + solidrectangle( (int)round(part->box.lefttop.x), (int)round(part->box.lefttop.y), (int)round(part->box.lefttop.x + part->box.size.x), (int)round(part->box.lefttop.y + part->box.size.y)); - render_DrawTextEx(b->label, part->box, (b->left_aligned ? DT_LEFT : DT_CENTER) | DT_VCENTER | DT_SINGLELINE); + render_DrawTextEx(b->label, padded_box, (b->left_aligned ? DT_LEFT : DT_CENTER) | DT_VCENTER | DT_SINGLELINE); }