From: Randy McShandy Date: Sun, 15 Jun 2025 03:52:38 +0000 (-0500) Subject: Introduce main menu, menu control system, and get the Quit button working as a proof... X-Git-Url: http://git.mcshandy.xyz/gitweb.cgi?a=commitdiff_plain;h=6f7571ad7320a1638a8341641809d4fdb18f5b00;p=barrow_crawler Introduce main menu, menu control system, and get the Quit button working as a proof of concept. --- diff --git a/bin/posix_BC b/bin/posix_BC index d2ca837..0ce3cc5 100755 Binary files a/bin/posix_BC and b/bin/posix_BC differ diff --git a/src/enums.h b/src/enums.h new file mode 100644 index 0000000..503f6bf --- /dev/null +++ b/src/enums.h @@ -0,0 +1,78 @@ +#ifndef __LOCAL_ENUMS__ +#define __LOCAL_ENUMS__ + +typedef enum +{ + Square = 0, + Circle, + Barrow +} Shape; + +typedef enum +{ + STATUS_TEXT_FADE_TIME = 0, + STATUS_TIMERS +} eTimers; + +typedef enum +{ + UNKNOWN = 0, + GOOD, + BAD, + WAITING, + FINISHED, + STATES +} eState; + +typedef enum +{ + SAVE_FILE_LOAD = 0, + SAVE_FILE_SAVE, + + /* Resource load and generation here eventually too */ + + PROCESSES +} eProcess; + +typedef enum +{ + STATUS_TEXT_PLACE = 0, + TEXT_PLACES, +} eTextPlaces; + +typedef enum +{ + RENDER_MAIN_MENU = 0, + RENDER_RESOURCE_WAIT, + RENDER_RESOURCE_READY, + RENDER_GAME, + RENDER_MODES +} eRenderMode; + +typedef enum +{ + CONTROL_MAIN_MENU = 0, + CONTROL_RESOURCE_WAIT, + CONTROL_RESOURCE_READY, + CONTROL_GAME, + CONTROL_MODES +} eControlMode; + +#define MENU_ITEMS \ + X(MI_NONE) \ + X(MI_NEW_GAME) \ + X(MI_LOAD_GAME) \ + X(MI_SAVE_GAME) \ + X(MI_QUIT) \ + X(MI_ITEMS) + +#define X(item) item, +typedef enum +{ + MENU_ITEMS +} eMenuItem; +#undef X + + +#endif /* __LOCAL_ENUMS__ */ + diff --git a/src/main.c b/src/main.c index 6359d2b..c0949a1 100755 --- a/src/main.c +++ b/src/main.c @@ -39,7 +39,7 @@ int main(int argc, char** argv) start_render_loop(); pthread_join(generator_thread, NULL); #else - resource_state = 1; + resource_state = 0; start_render_loop(); #endif /* ENABLE_BARROWGEN */ diff --git a/src/menufuncs.c b/src/menufuncs.c new file mode 100644 index 0000000..76462db --- /dev/null +++ b/src/menufuncs.c @@ -0,0 +1,52 @@ +#include +#include +#include + +#include "structs.h" + +bool menu_action_MI_NONE() +{ return true; } + +bool menu_action_MI_NEW_GAME() +{ + bool response = false; + + + + return response; +} + +bool menu_action_MI_LOAD_GAME() +{ + bool response = false; + + + + return response; +} + +bool menu_action_MI_SAVE_GAME() +{ + bool response = false; + + + + return response; +} + +bool menu_action_MI_QUIT() +{ + bool response = false; + + printf("shutting down!\n"); + playtime.should_quit = 1; + + return response; +} + +bool menu_action_MI_ITEMS() +{ + /* Unreachable! */ + assert(0); +} + diff --git a/src/menufuncs.h b/src/menufuncs.h new file mode 100644 index 0000000..c82e776 --- /dev/null +++ b/src/menufuncs.h @@ -0,0 +1,19 @@ +#ifndef __MENUFUNCS__ +#define __MENUFUNCS__ + +#include +#include "enums.h" + +typedef bool(*menufunc)(); + +bool menu_action_MI_NONE(); +bool menu_action_MI_NEW_GAME(); +bool menu_action_MI_LOAD_GAME(); +bool menu_action_MI_SAVE_GAME(); +bool menu_action_MI_QUIT(); +bool menu_action_MI_ITEMS(); + +extern menufunc menufuncs[MI_ITEMS + 1]; + +#endif /* __MENU_FUNCS__ */ + diff --git a/src/render_raylib.c b/src/render_raylib.c index 533f49b..979171f 100644 --- a/src/render_raylib.c +++ b/src/render_raylib.c @@ -10,6 +10,7 @@ #include #include "structs.h" +#include "ui.h" #define str(x) #x #define xstr(x) str(x) @@ -119,9 +120,8 @@ int draw_collision_mesh = 0; typedef void(*renderfunc)(); typedef void(*controlfunc)(); -#define MAX_RENDERFUNCS 3U -renderfunc renderfuncs[MAX_RENDERFUNCS]; -controlfunc controlfuncs[MAX_RENDERFUNCS]; +renderfunc renderfuncs[RENDER_MODES]; +controlfunc controlfuncs[CONTROL_MODES]; Color ColorLerp(Color c1, Color c2, float amount) { @@ -135,6 +135,34 @@ Color ColorLerp(Color c1, Color c2, float amount) return new_color; } +void drawing_main_menu_mode() +{ + ClearBackground(LIGHTGRAY); + + const size_t title_font_size = 64; + const size_t sub_font_size = 48; + const char title_text[] = "Randy's Barrow Adventure"; + + DrawText(title_text, + (fscreen_dims.x/2.0f) - (MeasureText(title_text, title_font_size)/2.0f), (fscreen_dims.y/4.0f) * 1.0f, title_font_size, BLACK); + + for (size_t n = 0; n < 3; n++) + { + const MenuButton button = main_menu_items[n]; + const char* text = menu_items[button.item_index].text; + const Vector2 text_v2 = button.text_v2; + const Rectangle button_bound = button.button_bound; + + const bool mouse_in_container = menu_info.hover_item == button.item_index; + const Color fg = (mouse_in_container) ? button.fg : button.bg; + const Color bg = (mouse_in_container) ? button.bg : button.fg; + + DrawRectangle(button_bound.x, button_bound.y, button_bound.width, button_bound.height, bg); + DrawText(text, + text_v2.x, text_v2.y, sub_font_size, fg); + } +} + /* Render the regular game mode. NOTE: Only call inside a Raylib BeginDrawing() block! */ @@ -188,9 +216,9 @@ void drawing_game_mode() Rectangle minimap_dest = {.width = 64.0f*img_export_scale.x, .height = 64.0f*img_export_scale.y, .x = 0.0f, .y = 0.0f}; Rectangle minimap_src = {.width = barrow_texture.width, .height = barrow_texture.height, .x = 0.0f, .y = 0.0f}; - if (timers[E_STATUS_TEXT_FADE_TIME].time > 0.0) + if (timers[STATUS_TEXT_FADE_TIME].time > 0.0) { - const char* text = text_places[E_STATUS_TEXT_PLACE]; + const char* text = text_places[STATUS_TEXT_PLACE]; DrawText(TextFormat("%s", text), 0, screen_dims.y - 32, 32, GREEN); } @@ -314,44 +342,44 @@ void control_game_mode() playtime.cam.position = cam_reset_position; } - if (IsKeyReleased(KEY_F4) && timers[E_STATUS_TEXT_FADE_TIME].time == 0) + if (IsKeyReleased(KEY_F4) && timers[STATUS_TEXT_FADE_TIME].time == 0) { - ProcessInfo* process = &processes[E_SAVE_FILE_SAVE]; - processes[E_SAVE_FILE_SAVE].state = E_WAITING; + ProcessInfo* process = &processes[SAVE_FILE_SAVE]; + processes[SAVE_FILE_SAVE].state = WAITING; const int success = save_game(playtime); - if ((success == 0) && timers[E_STATUS_TEXT_FADE_TIME].time == 0) + if ((success == 0) && timers[STATUS_TEXT_FADE_TIME].time == 0) { - processes[E_SAVE_FILE_SAVE].state = E_FINISHED; + processes[SAVE_FILE_SAVE].state = FINISHED; } else if (success != 0) { - processes[E_SAVE_FILE_SAVE].state = E_BAD; + processes[SAVE_FILE_SAVE].state = BAD; } - memcpy(text_places[E_STATUS_TEXT_PLACE], process->info_text[process->state], 64); - timers[E_STATUS_TEXT_FADE_TIME].time = timers[E_STATUS_TEXT_FADE_TIME].max; + memcpy(text_places[STATUS_TEXT_PLACE], process->info_text[process->state], 64); + timers[STATUS_TEXT_FADE_TIME].time = timers[STATUS_TEXT_FADE_TIME].max; } - if (IsKeyReleased(KEY_F5) && timers[E_STATUS_TEXT_FADE_TIME].time == 0) + if (IsKeyReleased(KEY_F5) && timers[STATUS_TEXT_FADE_TIME].time == 0) { - ProcessInfo* process = &processes[E_SAVE_FILE_LOAD]; - process->state = E_WAITING; + ProcessInfo* process = &processes[SAVE_FILE_LOAD]; + process->state = WAITING; const int success = load_game(); - if ((success == 0 ) && timers[E_STATUS_TEXT_FADE_TIME].time == 0.0) + if ((success == 0 ) && timers[STATUS_TEXT_FADE_TIME].time == 0.0) { - process->state = E_FINISHED; + process->state = FINISHED; } else if (success != 0) { - process->state = E_BAD; + process->state = BAD; } - memcpy(text_places[E_STATUS_TEXT_PLACE], process->info_text[process->state], 64); - timers[E_STATUS_TEXT_FADE_TIME].time = timers[E_STATUS_TEXT_FADE_TIME].max; + memcpy(text_places[STATUS_TEXT_PLACE], process->info_text[process->state], 64); + timers[STATUS_TEXT_FADE_TIME].time = timers[STATUS_TEXT_FADE_TIME].max; } if (IsKeyReleased(KEY_ONE)) @@ -393,6 +421,7 @@ void control_resource_wait_mode() { } + void wait_initialize_shaders() { shader = LoadShader("./src/shaders/lighting.vs", "./src/shaders/lighting.fs"); @@ -570,18 +599,20 @@ void initialize_prerenderer() /* Mode handler setup */ { - renderfuncs[0U] = drawing_resource_wait_mode; - renderfuncs[1U] = drawing_resource_ready_mode; - renderfuncs[2U] = drawing_game_mode; - controlfuncs[0U] = control_resource_wait_mode; - controlfuncs[1U] = control_resource_ready_mode; - controlfuncs[2U] = control_game_mode; + renderfuncs[RENDER_MAIN_MENU] = drawing_main_menu_mode; + renderfuncs[RENDER_RESOURCE_WAIT] = drawing_resource_wait_mode; + renderfuncs[RENDER_RESOURCE_READY] = drawing_resource_ready_mode; + renderfuncs[RENDER_GAME] = drawing_game_mode; + controlfuncs[CONTROL_MAIN_MENU] = control_main_menu_mode; + controlfuncs[CONTROL_RESOURCE_WAIT] = control_resource_wait_mode; + controlfuncs[CONTROL_RESOURCE_READY] = control_resource_ready_mode; + controlfuncs[CONTROL_GAME] = control_game_mode; } } void update_timers() { - for (int t = 0; t < E_STATUS_TIMERS; t++) + for (int t = 0; t < STATUS_TIMERS; t++) { if (timers[t].time > 0) { @@ -592,9 +623,14 @@ void update_timers() void start_render_loop() { + /* Things that need to happen before Raylib init */ initialize_prerenderer(); + SetTraceLogLevel(LOG_ALL); InitWindow(fscreen_dims.x, fscreen_dims.y, screen_title); + + /* Things that need to happen after Raylib init */ + init_menus(fscreen_dims); wait_initialize_shaders(); SetTargetFPS(target_fps); @@ -605,7 +641,6 @@ void start_render_loop() int func_idx = resource_state; player_collide_point = playtime.cam.position; - //player_collide_point.y -= 1.0f; controlfuncs[func_idx](); SetShaderValue(shader, cam_position_shader_loc, &playtime.cam.position, SHADER_UNIFORM_VEC3); @@ -624,9 +659,9 @@ void start_render_loop() /* Decay forward velocity and rotation */ playtime.player_velocity.x = Lerp(playtime.player_velocity.x, 0.0f, forward_speed_decay * frame_time); playtime.player_rotation.x = Lerp(playtime.player_rotation.x, 0.0f, rotate_speed_decay * frame_time); - } - playtime.should_quit = 1; + if (playtime.should_quit) break; + } UnloadImage(barrow_image); UnloadTexture(barrow_texture); diff --git a/src/structs.c b/src/structs.c index 10e86f3..72e729f 100644 --- a/src/structs.c +++ b/src/structs.c @@ -14,7 +14,7 @@ RD_Opts puffer = .delta_t = 0.6f, .name = "puffer", - .shape = eCircle + .shape = Circle }; // diff values influence tightness, delta between them shouldn't get wider than ~0.5 or narrower than ~0.4 @@ -28,7 +28,7 @@ RD_Opts barrow = .delta_t = 0.6f, .name = "barrow", - .shape = eBarrow + .shape = Barrow }; RD_Opts worms = @@ -41,7 +41,7 @@ RD_Opts worms = .delta_t = 0.6f, .name = "worms", - .shape = eSquare + .shape = Square }; RD_Opts meiosis = @@ -54,7 +54,7 @@ RD_Opts meiosis = .delta_t = 1.0f, .name = "meiosis", - .shape = eSquare + .shape = Square }; const Mat3 laplacian_kernel = @@ -72,20 +72,21 @@ const Mat3 laplacian_kernel = PlaytimeData playtime; -Timer timers[E_STATUS_TIMERS] = +Timer timers[STATUS_TIMERS] = { /* timer time max */ /* E_STATUS_TEXT_FADE_TIME */ { 0, 4000 } }; -ProcessInfo processes[E_PROCESSES] = +ProcessInfo processes[PROCESSES] = { /* Process { STATE Progress, { E_UNKNOWN, E_GOOD, E_BAD, E_WAITING, E_FINISHED } } */ - /* E_SAVE_FILE_LOAD */ { E_UNKNOWN, 0.0f, { "", "", "Failed to load save.", "Loading...", "Loaded" } }, - /* E_SAVE_FILE_SAVE */ { E_UNKNOWN, 0.0f, { "", "", "Failed to create save.", "Saving...", "Saved" } } + /* E_SAVE_FILE_LOAD */ { UNKNOWN, 0.0f, { "", "", "Failed to load save.", "Loading...", "Loaded" } }, + /* E_SAVE_FILE_SAVE */ { UNKNOWN, 0.0f, { "", "", "Failed to create save.", "Saving...", "Saved" } } }; -char text_places[E_TEXT_PLACES][64]; + +char text_places[TEXT_PLACES][64]; const char chars[6] = {' ', ' ', ' ', '+', '#', '@'}; const int max_chars = sizeof(chars) / sizeof(chars[0]) - 1; @@ -96,5 +97,8 @@ const int target_fps = 60; const IVec2 img_export_scale = {.x = 2, .y = 2}; float resource_generation_progress = 0.0f; + +// Move this into menuinfo +// Actually merge all of them into a display manager int resource_state = 0; diff --git a/src/structs.h b/src/structs.h index eb4ff20..803c541 100644 --- a/src/structs.h +++ b/src/structs.h @@ -8,12 +8,8 @@ #include #include -typedef enum -{ - eSquare = 0, - eCircle, - eBarrow -} Shape; +#include "enums.h" +#include "ui.h" typedef struct { @@ -100,50 +96,19 @@ typedef struct float max; } Timer; -typedef enum -{ - E_STATUS_TEXT_FADE_TIME = 0, - E_STATUS_TIMERS -} eTimers; - -typedef enum -{ - E_UNKNOWN = 0, - E_GOOD, - E_BAD, - E_WAITING, - E_FINISHED, - E_STATES -} eState; - -typedef enum -{ - E_SAVE_FILE_LOAD = 0, - E_SAVE_FILE_SAVE, - - /* Resource load and generation here eventually too */ - - E_PROCESSES -} eProcess; - typedef struct { eState state; float progress; - char info_text[E_STATES][64]; + char info_text[STATES][64]; } ProcessInfo; -typedef enum -{ - E_STATUS_TEXT_PLACE = 0, - E_TEXT_PLACES, -} eTextPlaces; - extern PlaytimeData playtime; +extern MenuInfo menu_info; -extern ProcessInfo processes[E_PROCESSES]; -extern Timer timers[E_STATUS_TIMERS]; -extern char text_places[E_TEXT_PLACES][64]; +extern ProcessInfo processes[PROCESSES]; +extern Timer timers[STATUS_TIMERS]; +extern char text_places[TEXT_PLACES][64]; extern RD_Opts barrow; extern RD_Opts puffer; diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..22e2ff6 --- /dev/null +++ b/src/ui.c @@ -0,0 +1,97 @@ +#include +#include + +#include "menufuncs.h" +#include "ui.h" + +const MenuItem menu_items[MI_ITEMS] = +{ + { MI_NONE, "" , false}, + { MI_NEW_GAME, "New Game" , false}, + { MI_LOAD_GAME, "Load Game" , false}, + { MI_SAVE_GAME, "Save Game" , false}, + { MI_QUIT, "Quit" , false}, +}; + +MenuButton main_menu_items[3] = +{ + { MI_NEW_GAME , (Rectangle){}, (Vector2){}, DARKGRAY, LIGHTGRAY, 48 }, + { MI_LOAD_GAME, (Rectangle){}, (Vector2){}, DARKGRAY, LIGHTGRAY, 48 }, + { MI_QUIT , (Rectangle){}, (Vector2){}, DARKGRAY, LIGHTGRAY, 48 } +}; + +MenuInfo menu_info = +{ + RENDER_MAIN_MENU, + CONTROL_MAIN_MENU, + MI_NONE +}; + +menufunc menufuncs[MI_ITEMS+1]; + +void init_menus(Vector2 screen_dims) +{ + for (size_t n = 0; n < sizeof(main_menu_items)/sizeof(main_menu_items[0]); n++) + { + MenuButton* button = &main_menu_items[n]; + const MenuItem* menu_item = &menu_items[button->item_index]; + const int text_width = MeasureText(menu_item->text, button->font_size); + const Vector2 text_size = {.x = text_width, .y = button->font_size}; + + Vector2 text_v2 = + { + (screen_dims.x/2.0f) - (text_size.x / 2.0f), + (screen_dims.y/13.0f) * (n+5), + }; + + const uint32_t margin = 8U; + Rectangle button_bound = + (Rectangle){ + .x = text_v2.x - margin, + .y = text_v2.y - margin, + .width = text_size.x + margin * 2U, + .height = text_size.y + margin, + }; + + button->button_bound = button_bound; + button->text_v2 = text_v2; + + } + +#define X(emenu_item) menufuncs[emenu_item] = menu_action_ ## emenu_item; + MENU_ITEMS +#undef X + +} + +void control_main_menu_mode() +{ + const Vector2 mouse = GetMousePosition(); + const bool left_click = IsMouseButtonReleased(MOUSE_LEFT_BUTTON); + + /* Figure out a click in menu item bounds + Set globals + Let renderfunc act on it + */ + + menu_info.hover_item = MI_NONE; + for (size_t n = 0; n < sizeof(main_menu_items)/sizeof(main_menu_items[0]); n++) + { + const MenuButton button = main_menu_items[n]; + const Rectangle button_bound = button.button_bound; + const bool mouse_in_container = CheckCollisionPointRec(mouse, button_bound); + + if (mouse_in_container) + { + menu_info.hover_item = button.item_index; + menu_info.hover_item_picked = menu_info.hover_item && left_click; + } + + if ((menu_info.hover_item == button.item_index) && (menu_info.hover_item_picked)) + { + menufuncs[menu_info.hover_item](); + } + } + +} + diff --git a/src/ui.h b/src/ui.h new file mode 100644 index 0000000..446f865 --- /dev/null +++ b/src/ui.h @@ -0,0 +1,45 @@ +#ifndef __UI__ +#define __UI__ + +#include +#include + +#include "enums.h" + +typedef struct +{ + eRenderMode render_mode; + eControlMode control_mode; + eMenuItem hover_item; + bool hover_item_picked; +} MenuInfo; + +typedef struct +{ + eMenuItem type; + char text[64]; + bool selected; +} MenuItem; + +typedef struct +{ + eMenuItem item_index; + Rectangle button_bound; + Vector2 text_v2; + Color bg; + Color fg; + int font_size; +} MenuButton; + +extern MenuInfo menu_info; +extern const MenuItem menu_items[MI_ITEMS]; +extern MenuButton main_menu_items[3]; + +void init_menus(Vector2 screen_dims); +void control_main_menu_mode(); + +#define stringify2(x) #x +#define stringify(x) stringify2(x) + +#endif /* __UI__ */ +