This commit is contained in:
Edgaru089 2024-04-24 21:30:28 +08:00
parent 24294d45a9
commit 317ad22de0
13 changed files with 401 additions and 29 deletions

22
app.c
View File

@ -11,6 +11,7 @@
#include "render_bundle.h" #include "render_bundle.h"
#include "render_component.h" #include "render_component.h"
#include "mapper_misc.h" #include "mapper_misc.h"
#include "ui.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -27,6 +28,9 @@ App *app_NewApp() {
app->camera = camera_NewSystem(app); app->camera = camera_NewSystem(app);
app->particle = particle_NewSystem(app); app->particle = particle_NewSystem(app);
app->time = gametime_NewSystem(app); app->time = gametime_NewSystem(app);
app->ui = ui_NewSystem(app);
ui_RebuildUI(app->ui);
ui_PushState(app->ui, ui_Running);
app->switch_level = NULL; app->switch_level = NULL;
app->timescale = 1.0; app->timescale = 1.0;
@ -57,6 +61,7 @@ void app_DeleteApp(App *app) {
camera_DeleteSystem(app->camera); camera_DeleteSystem(app->camera);
particle_DeleteSystem(app->particle); particle_DeleteSystem(app->particle);
gametime_DeleteSystem(app->time); gametime_DeleteSystem(app->time);
ui_DeleteSystem(app->ui);
free(app); free(app);
} }
@ -72,15 +77,18 @@ void app_Advance(App *app, Duration deltaTime) {
input_Advance(app->input); input_Advance(app->input);
Duration delta_game = deltaTime;
if (1.0 - app->timescale > EPS) if (1.0 - app->timescale > EPS)
deltaTime.microseconds = deltaTime.microseconds * app->timescale; delta_game.microseconds = delta_game.microseconds * app->timescale;
if (!app->paused) { if (!app->paused) {
gametime_Advance(app->time, deltaTime); gametime_Advance(app->time, delta_game);
particle_Advance(app->particle, deltaTime); particle_Advance(app->particle, delta_game);
player_Advance(app->player, deltaTime); player_Advance(app->player, delta_game);
physics_Advance(app->physics, deltaTime); physics_Advance(app->physics, delta_game);
entity_Advance(app->entity, deltaTime); entity_Advance(app->entity, delta_game);
camera_Advance(app->camera, deltaTime); camera_Advance(app->camera, delta_game);
} }
ui_Advance(app->ui, deltaTime);
} }

2
app.h
View File

@ -7,6 +7,7 @@
#include "input.h" #include "input.h"
#include "camera.h" #include "camera.h"
#include "particle.h" #include "particle.h"
#include "ui.h"
#include "types.h" #include "types.h"
#include "util/vector.h" #include "util/vector.h"
@ -25,6 +26,7 @@ typedef struct _App {
System_Camera *camera; System_Camera *camera;
System_Particle *particle; System_Particle *particle;
System_GameTime *time; System_GameTime *time;
System_UI *ui;
char *switch_level; char *switch_level;
double timescale; double timescale;

View File

@ -7,6 +7,7 @@
#include "easyx.h" #include "easyx.h"
#include "render_bundle.h" #include "render_bundle.h"
#include "render_component.h" #include "render_component.h"
#include "ui.h"
#include "util/tree.h" #include "util/tree.h"
#include "types.h" #include "types.h"
#include "render_util.h" #include "render_util.h"
@ -21,10 +22,7 @@ TimePoint since = time_Now();
} // namespace } // namespace
extern "C" { extern "C" void app_Render(App *app) {
void app_Render(App *app) {
render_SetModes(render_ModeDefault, time_Now()); render_SetModes(render_ModeDefault, time_Now());
setbkcolor(app->clear_color); setbkcolor(app->clear_color);
cleardevice(); cleardevice();
@ -176,11 +174,13 @@ void app_Render(App *app) {
// If paused, display a text // If paused, display a text
if (app->paused) if (app->paused)
render_DrawTextEx("Game Paused", box2(SCREEN_WIDTH / 2 - 10, 100, 20, 100), DT_CENTER | DT_NOCLIP); render_DrawTextEx("Game Paused", box2(SCREEN_WIDTH / 2.0 - 10, 100, 20, 100), DT_CENTER | DT_NOCLIP);
if (1.0 - app->timescale > EPS) { if (1.0 - app->timescale > EPS) {
char buf[128]; char buf[128];
snprintf(buf, sizeof(buf), "*** TIMESCALE %.2lf ***", app->timescale); snprintf(buf, sizeof(buf), "*** TIMESCALE %.2lf ***", app->timescale);
render_DrawTextEx(buf, box2(SCREEN_WIDTH / 2 - 10, 50, 20, 100), DT_CENTER | DT_NOCLIP); render_DrawTextEx(buf, box2(SCREEN_WIDTH / 2.0 - 10, 50, 20, 100), DT_CENTER | DT_NOCLIP);
}
} }
// Draw UI
ui_Render(app->ui);
} }

15
input.c
View File

@ -1,6 +1,7 @@
#include "input.h" #include "input.h"
#include "app.h" #include "app.h"
#include "ui.h"
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
@ -75,11 +76,15 @@ void input_Advance(System_Input *sys) {
} }
if (sys->keys[input_Key_Escape] == JustPressed) { if (sys->keys[input_Key_Escape] == JustPressed) {
if (!sys->super->paused) if (ui_CurrentState(sys->super->ui) == ui_Running) {
fprintf(stderr, "[input_Advance] Pausing\n"); // Pause
else ui_PushState(sys->super->ui, ui_Paused);
fprintf(stderr, "[input_Advance] Unpausing\n"); sys->super->paused = true;
sys->super->paused = !sys->super->paused; } else if (ui_CurrentState(sys->super->ui) == ui_Paused) {
// Unpause
ui_PopState(sys->super->ui);
sys->super->paused = false;
}
} }
} }

View File

@ -11,8 +11,8 @@ extern "C" {
#endif #endif
#define SCREEN_WIDTH 1600 #define SCREEN_WIDTH 1536
#define SCREEN_HEIGHT 900 #define SCREEN_HEIGHT 864
#define WARN(fmt, ...) fprintf(stderr, "[WARN][%s] " fmt "\n", __func__, ##__VA_ARGS__) #define WARN(fmt, ...) fprintf(stderr, "[WARN][%s] " fmt "\n", __func__, ##__VA_ARGS__)
#define INFO(fmt, ...) fprintf(stderr, "[%s] " fmt "\n", __func__, ##__VA_ARGS__) #define INFO(fmt, ...) fprintf(stderr, "[%s] " fmt "\n", __func__, ##__VA_ARGS__)

127
ui.c Normal file
View File

@ -0,0 +1,127 @@
#include "ui.h"
#include "input.h"
#include "app.h"
#include "types.h"
#include "util/vector.h"
#include <stdio.h>
const char *ui_StateTitle[ui_StateCount] = {
"Running",
"Pause Menu",
"Jack's Escape (ver. 3.141592) - Title",
"Select Level",
"Options"};
static inline double dabs(double x) { return x > 0 ? x : -x; }
static inline double dmin(double x, double y) { return x < y ? x : y; }
static inline double dmax(double x, double y) { return x > y ? x : y; }
System_UI *ui_NewSystem(App *super) {
System_UI *sys = zero_malloc(sizeof(System_UI));
sys->super = super;
sys->state = vector_Create(sizeof(ui_State));
for (int i = 0; i < ui_StateCount; i++)
sys->parts[i] = vector_Create(sizeof(ui_Part));
return sys;
}
void ui_DeleteSystem(System_UI *sys) {
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);
part->free(sys, part, part->user);
}
for (int i = 0; i < ui_StateCount; i++)
vector_Destroy(sys->parts[i]);
vector_Destroy(sys->state);
free(sys);
}
void ui_PushPart(System_UI *sys, ui_State layer, ui_Part part) {
vector_Push(sys->parts[layer], &part);
}
static inline double fadeto(double from, double to, Duration deltaTime) {
if (dabs(from - to) < EPS)
return to;
else
return from + (to - from) * UI_BACKGROUND_FADE_MOVE_SPEED * duration_Seconds(deltaTime);
}
static void _ui_UpdateBgTarget(System_UI *sys) {
if (vector_Size(sys->state) == 0) {
WARN("state stack is empty");
return;
}
ui_State layer = *(ui_State *)vector_Back(sys->state);
if (vector_Size(sys->parts[layer]) == 0) {
// No UI target, set to (0,0), (0,0)
memset(&sys->bg_target, 0, sizeof(sys->bg_target));
return;
}
Vec2 min = {.x = SCREEN_WIDTH, .y = SCREEN_HEIGHT}, max = {.x = 0, .y = 0};
for (int i = 0; i < vector_Size(sys->parts[layer]); i++) {
ui_Part *part = (ui_Part *)vector_At(sys->parts[layer], i);
min.x = dmin(min.x, part->box.lefttop.x);
min.y = dmin(min.y, part->box.lefttop.y);
max.x = dmax(max.x, part->box.lefttop.x + part->box.size.x);
max.y = dmax(max.y, part->box.lefttop.y + part->box.size.y);
}
sys->bg_target.lefttop = vec2(min.x - UI_PADDING, min.y - UI_PADDING);
sys->bg_target.size = vec2(max.x - min.x + 2 * UI_PADDING, max.y - min.y + 2 * UI_PADDING);
}
void ui_Advance(System_UI *sys, Duration deltaTime) {
Vec2 lt = sys->bg.lefttop, rb = vec2_Add(sys->bg.lefttop, sys->bg.size);
Vec2 lt_target = sys->bg_target.lefttop, rb_target = vec2_Add(sys->bg_target.lefttop, sys->bg_target.size);
lt.x = fadeto(lt.x, lt_target.x, deltaTime);
lt.y = fadeto(lt.y, lt_target.y, deltaTime);
rb.x = fadeto(rb.x, rb_target.x, deltaTime);
rb.y = fadeto(rb.y, rb_target.y, 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);
}
}
void ui_PushState(System_UI *sys, ui_State push_state) {
vector_Push(sys->state, &push_state);
_ui_UpdateBgTarget(sys);
}
void ui_PopState(System_UI *sys) {
if (vector_Size(sys->state) == 0)
WARN("state stack empty");
else {
vector_Pop(sys->state, NULL);
_ui_UpdateBgTarget(sys);
}
}
ui_State ui_CurrentState(System_UI *sys) {
if (vector_Size(sys->state) == 0)
WARN("state stack empty");
return *(ui_State *)vector_Back(sys->state);
}

72
ui.h
View File

@ -15,26 +15,86 @@ typedef enum {
ui_Running, ui_Running,
ui_Paused, ui_Paused,
ui_TitleMenu, ui_TitleMenu,
ui_SceneSelect,
ui_Options,
ui_StateCount // Keep at last ui_StateCount // Keep at last
} ui_States; } ui_State;
extern const char *ui_StateTitle[ui_StateCount];
// Forward declarations // Forward declarations
typedef struct _ui_Button ui_Button;
typedef struct _System_UI System_UI; typedef struct _System_UI System_UI;
// a Part of the UI. // a Part of the UI.
typedef struct { typedef struct _ui_Part {
void *user; // user data Box2 box; // bounding box of the UI element
void (*draw)(System_UI *sys, void *user); // called when the UI is to be drawn float progress; // hover process, from 0 to 1
bool hovered; // Whether the part is hovered
uintptr_t user; // user data
void (*draw)(System_UI *sys, struct _ui_Part *part, uintptr_t user); // called when the UI is to be drawn
void (*update)(System_UI *sys, struct _ui_Part *part, uintptr_t user, Duration deltatime); // called when the element is to be updated
void (*free)(System_UI *sys, struct _ui_Part *part, uintptr_t user); // called when the element is to be deleted
} ui_Part; } ui_Part;
// UI system with its own state machine. // UI system with its own state machine.
typedef struct _System_UI { typedef struct _System_UI {
App *super; App *super;
vector_Vector *parts[ui_StateCount]; vector_Vector *parts[ui_StateCount]; // A vector of ui_Parts
vector_Vector *state; // A stack of ui_States
Box2 bg, bg_target;
} System_UI; } System_UI;
System_UI *ui_NewSystem(App *super);
void ui_DeleteSystem(System_UI *sys);
// Clears and refills every menu with its widgets
void ui_RebuildUI(System_UI *sys);
void ui_PushPart(System_UI *sys, ui_State layer, ui_Part part);
void ui_Advance(System_UI *sys, Duration deltaTime);
void ui_Render(System_UI *sys);
void ui_PushState(System_UI *sys, ui_State push_state);
void ui_PopState(System_UI *sys);
ui_State ui_CurrentState(System_UI *sys);
#define UI_PADDING (20)
#define UI_BACKGROUND (0)
#define UI_BACKGROUND_FADE_MOVE_SPEED (20.0)
#define UI_BUTTON_HOVER_SPEED (5.0)
#define UI_BUTTON_BACKGROUND (RGB(50, 50, 50))
#define UI_BUTTON_HOVERED (RGB(75, 75, 75))
#define UI_BUTTON_CLICKED (RGB(90, 90, 90))
typedef void (*ui_ActionCallback)(System_UI *sys, ui_Part *part, uintptr_t data);
// An Action to pop the UI state stack once
void ui_Action_PopState(System_UI *sys, ui_Part *part, uintptr_t data);
// An Action to push the state, casted directly from data
void ui_Action_PushState(System_UI *sys, ui_Part *part, uintptr_t data);
// Allocated & passed in as user data for ui part
typedef struct _ui_Button {
ui_ActionCallback callback;
uintptr_t callback_data;
uint32_t bg, bg_hover, clicked;
char *label; // Allocated & copied
bool left_aligned;
} ui_Button;
ui_Part ui_Button_New(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 ui_Button_NewColored(Box2 box, const char *label, uint32_t bg, uint32_t hover, uint32_t clicked, ui_ActionCallback callback, uintptr_t data);
#ifdef __cplusplus #ifdef __cplusplus
} }

10
ui_action.c Normal file
View File

@ -0,0 +1,10 @@
#include "ui.h"
void ui_Action_PopState(System_UI *sys, ui_Part *part, uintptr_t data) {
ui_PopState(sys);
}
void ui_Action_PushState(System_UI *sys, ui_Part *part, uintptr_t data) {
ui_PushState(sys, (ui_State)data);
}

29
ui_build.c Normal file
View File

@ -0,0 +1,29 @@
#include "ui.h"
#include "app.h"
#include "util/vector.h"
static void _ui_Action_ResumeGame(System_UI *sys, ui_Part *part, uintptr_t data) {
ui_Action_PopState(sys, part, data);
sys->super->paused = false;
}
#define PUSH_UI(part) \
_p0 = (part); \
vector_Push(p, &_p0);
void ui_RebuildUI(System_UI *sys) {
vector_Vector *p;
ui_Part _p0;
// 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)),
"Resume Game",
&_ui_Action_ResumeGame, 0))
}

45
ui_button.c Normal file
View File

@ -0,0 +1,45 @@
#include "input.h"
#include "types.h"
#include "ui.h"
#include "app.h"
#ifndef RGB
#define RGB(r, g, b) ((r) | ((g) << 8) | ((b) << 16))
#endif
void _ui_Button_Draw(System_UI *sys, ui_Part *part, uintptr_t user); // Defined in ui_render.cpp
void _ui_Button_Update(System_UI *sys, ui_Part *part, uintptr_t user, Duration deltatime) {
ui_Button *b = (ui_Button *)user;
if (part->hovered && sys->super->input->keys[input_Key_LeftMouse] == JustReleased)
b->callback(sys, part, b->callback_data);
}
void _ui_Button_Free(System_UI *sys, ui_Part *part, uintptr_t user) {
ui_Button *b = (ui_Button *)user;
free(b->label);
free(b);
}
ui_Part ui_Button_New(Box2 box, const char *label, ui_ActionCallback callback, uintptr_t data) {
ui_Button *b = zero_malloc(sizeof(ui_Button));
b->callback = callback;
b->callback_data = data;
b->bg = UI_BUTTON_BACKGROUND;
b->bg_hover = UI_BUTTON_HOVERED;
b->clicked = UI_BUTTON_CLICKED;
b->label = copy_malloc(label);
b->left_aligned = false;
ui_Part part = {
.box = box,
.progress = 0.0f,
.user = (uintptr_t)b,
.draw = &_ui_Button_Draw,
.update = &_ui_Button_Update,
.free = &_ui_Button_Free};
return part;
}
ui_Part ui_Button_NewLeftAligned(Box2 box, const char *label, ui_ActionCallback callback, uintptr_t data);

75
ui_render.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "input.h"
#include "render_util.h"
#include "types.h"
#include "ui.h"
#include "app.h"
#include <easyx.h>
extern "C" void ui_Render(System_UI *sys) {
// Background filled rectangle
if (sys->bg.size.x > EPS && sys->bg.size.y > EPS) {
setfillcolor(UI_BACKGROUND);
solidrectangle(
(int)round(sys->bg.lefttop.x),
(int)round(sys->bg.lefttop.y),
(int)round(sys->bg.lefttop.x + sys->bg.size.x),
(int)round(sys->bg.lefttop.y + sys->bg.size.y));
}
if (vector_Size(sys->state) == 0)
return;
ui_State layer = *(ui_State *)vector_Back(sys->state);
// Title string
setbkcolor(UI_BACKGROUND);
render_DrawText(
(int)round(sys->bg.lefttop.x),
(int)round(sys->bg.lefttop.y) - TEXTHEIGHT,
ui_StateTitle[layer]);
// UI elements
// Skip them if the width of background is less than 90% of the target
if (sys->bg.size.x > sys->bg_target.size.x * 0.9)
for (int i = 0; i < vector_Size(sys->parts[layer]); i++) {
ui_Part *part = (ui_Part *)vector_At(sys->parts[layer], i);
part->draw(sys, part, part->user);
}
}
extern "C" void _ui_Button_Draw(System_UI *sys, ui_Part *part, void *user) {
ui_Button *b = (ui_Button *)user;
uint32_t color_final = 0;
if (part->hovered && input_IsPressed(sys->super->input->keys[input_Key_LeftMouse]))
// Hovered & pressed
color_final = b->clicked;
else {
// Fade the color
int r0 = b->bg & 0xff;
int g0 = (b->bg & 0xff00) >> 8;
int b0 = (b->bg & 0xff0000) >> 16;
int r1 = b->bg_hover & 0xff;
int g1 = (b->bg_hover & 0xff00) >> 8;
int b1 = (b->bg_hover & 0xff0000) >> 16;
color_final = RGB(
(int)((r1 - r0) * sqrt(part->progress) + r0),
(int)((g1 - g0) * sqrt(part->progress) + g0),
(int)((b1 - b0) * sqrt(part->progress) + b0));
}
setbkcolor(color_final);
setfillcolor(color_final);
settextcolor(RGB(255, 255, 255));
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);
}

View File

@ -3,6 +3,7 @@
#include "string.h" #include "string.h"
#include "stdlib.h" #include "stdlib.h"
#include "assert.h"
vector_Vector *vector_Create(uintptr_t objectSize) { vector_Vector *vector_Create(uintptr_t objectSize) {
@ -19,6 +20,7 @@ vector_Vector *vector_Create(uintptr_t objectSize) {
// Resizes the underlying buffer to a new capacity // Resizes the underlying buffer to a new capacity
static inline void __vector_Rebuffer(vector_Vector *vec, uintptr_t newcap) { static inline void __vector_Rebuffer(vector_Vector *vec, uintptr_t newcap) {
ASSERT(newcap >= vec->size);
void *newbuf = malloc(newcap); void *newbuf = malloc(newcap);
memcpy(newbuf, vec->data, vec->size); memcpy(newbuf, vec->data, vec->size);
free(vec->data); free(vec->data);
@ -50,7 +52,7 @@ bool vector_Pop(vector_Vector *vec, void *out_data) {
void vector_Append(vector_Vector *vec, const void *data, uintptr_t n) { void vector_Append(vector_Vector *vec, const void *data, uintptr_t n) {
uintptr_t oldsize = vec->size, addsize = vec->objectSize * n; uintptr_t oldsize = vec->size, addsize = vec->objectSize * n;
vector_Resize(vec, oldsize + addsize); vector_Resize(vec, vector_Size(vec) + n);
if (data) if (data)
memcpy(vec->data + oldsize, data, addsize); memcpy(vec->data + oldsize, data, addsize);
@ -59,7 +61,7 @@ void vector_Append(vector_Vector *vec, const void *data, uintptr_t n) {
} }
void vector_Resize(vector_Vector *vec, uintptr_t size) { void vector_Resize(vector_Vector *vec, uintptr_t size) {
uintptr_t newsize = size; uintptr_t newsize = size * vec->objectSize;
if (newsize > vec->cap) { if (newsize > vec->cap) {
// grow the buffer exponentially // grow the buffer exponentially
uint64_t newcap = vec->cap; uint64_t newcap = vec->cap;
@ -101,3 +103,7 @@ void *vector_At(vector_Vector *vec, uintptr_t i) {
void *vector_Data(vector_Vector *vec) { void *vector_Data(vector_Vector *vec) {
return vec->data; return vec->data;
} }
void *vector_Back(vector_Vector *vec) {
return vector_At(vec, vector_Size(vec) - 1);
}

View File

@ -68,6 +68,11 @@ void *vector_At(vector_Vector *vec, uintptr_t i);
// Data returns the data buffer. // Data returns the data buffer.
void *vector_Data(vector_Vector *vec); void *vector_Data(vector_Vector *vec);
// Back returns the last element in the vector.
//
// No boundary test is performed.
void *vector_Back(vector_Vector *vec);
#ifdef __cplusplus #ifdef __cplusplus
} }