feat(gui): add toolbar widget

feat(gui): add grid_select, toggle, panel, status_bar, image widgets
fix(bsp): fill in exterior cells
This commit is contained in:
asrael 2026-02-27 06:50:49 -06:00
parent 5a565844dd
commit 8d491612ab
63 changed files with 3150 additions and 1686 deletions

View file

@ -4,16 +4,13 @@
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include "pxl8_cart.h"
#include "pxl8_embed.h"
#include "pxl8_io.h"
#include "pxl8_gui.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
@ -28,7 +25,7 @@ struct pxl8_script {
char last_error[PXL8_MAX_ERROR_SIZE];
char main_path[PXL8_MAX_PATH];
char watch_dir[PXL8_MAX_PATH];
time_t latest_mod_time;
f64 latest_mod_time;
int repl_env_ref;
bool repl_mode;
};
@ -407,8 +404,12 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil
char script_dir[PATH_MAX];
char original_cwd[PATH_MAX];
if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) {
chdir(script_dir);
char* resolved = pxl8_io_get_real_path(filename_copy);
char* cwd = pxl8_io_get_cwd();
if (resolved && cwd) {
pxl8_strncpy(script_dir, resolved, sizeof(script_dir));
pxl8_strncpy(original_cwd, cwd, sizeof(original_cwd));
pxl8_io_set_cwd(script_dir);
pxl8_script_set_cart_path(script, script_dir, original_cwd);
strncpy(out_basename, last_slash + 1, basename_size - 1);
out_basename[basename_size - 1] = '\0';
@ -416,6 +417,8 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil
strncpy(out_basename, filename, basename_size - 1);
out_basename[basename_size - 1] = '\0';
}
pxl8_free(resolved);
pxl8_free(cwd);
} else {
strncpy(out_basename, filename, basename_size - 1);
out_basename[basename_size - 1] = '\0';
@ -442,51 +445,38 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
return result;
}
static time_t get_file_mod_time(const char* path) {
struct stat file_stat;
if (stat(path, &file_stat) == 0) {
return file_stat.st_mtime;
}
return 0;
}
typedef struct {
f64 latest;
} script_mod_ctx;
static time_t get_latest_script_mod_time(const char* dir_path) {
DIR* dir = opendir(dir_path);
if (!dir) return 0;
static f64 get_latest_script_mod_time(const char* dir_path);
time_t latest = 0;
struct dirent* entry;
static bool check_script_mod_time(void* userdata, const char* dirname, const char* name) {
script_mod_ctx* ctx = userdata;
if (name[0] == '.') return true;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') continue;
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s%s", dirname, name);
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
time_t subdir_time = get_latest_script_mod_time(full_path);
if (subdir_time > latest) {
latest = subdir_time;
}
} else {
usize len = strlen(entry->d_name);
bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0);
if (is_script) {
time_t mod_time = get_file_mod_time(full_path);
if (mod_time > latest) {
latest = mod_time;
}
}
}
if (pxl8_io_is_directory(full_path)) {
f64 subdir_time = get_latest_script_mod_time(full_path);
if (subdir_time > ctx->latest) ctx->latest = subdir_time;
} else {
usize len = strlen(name);
bool is_script = (len > 4 && strcmp(name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(name + len - 4, ".lua") == 0);
if (is_script) {
f64 mod_time = pxl8_io_get_file_modified_time(full_path);
if (mod_time > ctx->latest) ctx->latest = mod_time;
}
}
return true;
}
closedir(dir);
return latest;
static f64 get_latest_script_mod_time(const char* dir_path) {
script_mod_ctx ctx = { .latest = 0.0 };
pxl8_io_enumerate_directory(dir_path, check_script_mod_time, &ctx);
return ctx.latest;
}
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) {
@ -1157,8 +1147,8 @@ bool pxl8_script_check_reload(pxl8_script* script) {
return false;
}
time_t current_mod_time = get_latest_script_mod_time(script->watch_dir);
if (current_mod_time > script->latest_mod_time && current_mod_time != 0) {
f64 current_mod_time = get_latest_script_mod_time(script->watch_dir);
if (current_mod_time > script->latest_mod_time && current_mod_time != 0.0) {
pxl8_info("Script files modified, reloading: %s", script->main_path);
script->latest_mod_time = current_mod_time;
@ -1214,12 +1204,6 @@ static pxl8_resolution parse_resolution(const char* str) {
return PXL8_RESOLUTION_640x360;
}
static pxl8_pixel_mode parse_pixel_mode(const char* str) {
if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED;
if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR;
return PXL8_PIXEL_INDEXED;
}
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) {
if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER;
@ -1267,12 +1251,6 @@ pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart)
}
lua_pop(script->L, 1);
lua_getfield(script->L, -1, "pixel-mode");
if (lua_isstring(script->L, -1)) {
pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1)));
}
lua_pop(script->L, 1);
lua_getfield(script->L, -1, "window-size");
if (lua_istable(script->L, -1)) {
lua_rawgeti(script->L, -1, 1);

View file

@ -27,9 +27,8 @@ static const char* pxl8_ffi_cdefs =
"f32 pxl8_get_fps(const pxl8* sys);\n"
"\n"
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
"u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);\n"
"u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);\n"
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
"u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx);\n"
"const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);\n"
"u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);\n"
"typedef struct pxl8_palette pxl8_palette;\n"
@ -262,6 +261,7 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
"void pxl8_3d_clear_stencil(pxl8_gfx* gfx, uint8_t value);\n"
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
@ -309,9 +309,20 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
"typedef struct pxl8_gfx_draw_opts {\n"
" bool color_write;\n"
" int depth_compare;\n"
" bool depth_write;\n"
" bool stencil_test;\n"
" bool stencil_write;\n"
" uint8_t stencil_compare;\n"
" uint8_t stencil_ref;\n"
"} pxl8_gfx_draw_opts;\n"
"\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts);\n"
"\n"
"u32 pxl8_hash32(u32 x);\n"
"u32 pxl8_chunk_hash(i32 cx, i32 cz);\n"
"\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
@ -401,6 +412,7 @@ static const char* pxl8_ffi_cdefs =
"\n"
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n"
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n"
"u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
"\n"
"typedef struct pxl8_world_chunk {\n"
@ -437,6 +449,7 @@ static const char* pxl8_ffi_cdefs =
"} pxl8_sim_entity;\n"
"\n"
"void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n"
"void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);\n"
"pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n"
"\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
@ -450,11 +463,17 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n"
"bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, float* value, float min_val, float max_val);\n"
"bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);\n"
"i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 btn_w, i32 btn_h, const char** labels, i32 count, i32 selected);\n"
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
"u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);\n"
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
"i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 cell_w, i32 cell_h, i32 cols, i32 rows, const u8* colors, i32 selected);\n"
"void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, i32 dx, i32 dy, i32 dw, i32 dh);\n"
"void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n"
"void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);\n"
"bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label, bool active);\n"
"\n"
"typedef struct pxl8_save pxl8_save;\n"
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
@ -514,7 +533,7 @@ static const char* pxl8_ffi_cdefs =
"\n"
"typedef struct pxl8_net pxl8_net;\n"
"typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, PXL8_CMD_ENTER_SCENE } pxl8_cmd_type;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
"\n"
"typedef struct pxl8_command_msg {\n"
" u16 cmd_type;\n"
@ -548,7 +567,10 @@ static const char* pxl8_ffi_cdefs =
"} pxl8_sim_config;\n"
"\n"
"typedef struct pxl8_sim_world {\n"
" const pxl8_bsp* bsp;\n"
" const pxl8_bsp* chunks[9];\n"
" i32 center_cx;\n"
" i32 center_cz;\n"
" f32 chunk_size;\n"
"} pxl8_sim_world;\n"
"\n"
"void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
@ -587,15 +609,15 @@ static const char* pxl8_ffi_cdefs =
"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
"bool pxl8_net_needs_correction(const pxl8_net* net);\n"
"u64 pxl8_net_player_id(const pxl8_net* net);\n"
"u8 pxl8_net_chunk_type(const pxl8_net* net);\n"
"u32 pxl8_net_chunk_id(const pxl8_net* net);\n"
"i32 pxl8_net_chunk_cx(const pxl8_net* net);\n"
"i32 pxl8_net_chunk_cz(const pxl8_net* net);\n"
"bool pxl8_net_has_chunk(const pxl8_net* net);\n"
"bool pxl8_net_poll(pxl8_net* net);\n"
"u8* pxl8_net_predicted_state(pxl8_net* net);\n"
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
"i32 pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z);\n"
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
"u64 pxl8_net_tick(const pxl8_net* net);\n"
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"