add ui module
This commit is contained in:
parent
1744e689b5
commit
6008ebf5ed
12 changed files with 703 additions and 215 deletions
20
demo/ui_demo.fnl
Normal file
20
demo/ui_demo.fnl
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
(local pxl8 (require :pxl8))
|
||||
|
||||
(var button-clicks 0)
|
||||
|
||||
(global init (fn []
|
||||
(pxl8.load_palette "palettes/gruvbox.ase")))
|
||||
|
||||
(global update (fn [_dt]))
|
||||
|
||||
(global frame (fn []
|
||||
(pxl8.clr 1)
|
||||
|
||||
(when pxl8.ui
|
||||
(when (pxl8.ui_window_begin "UI Demo" 20 20 280 150)
|
||||
(pxl8.ui_layout_row 1 0 0)
|
||||
(pxl8.ui_label "Welcome to some window UI!")
|
||||
(pxl8.ui_label (.. "Clicks: " button-clicks))
|
||||
(when (pxl8.ui_button "Click me!")
|
||||
(set button-clicks (+ button-clicks 1)))
|
||||
(pxl8.ui_window_end)))))
|
||||
3
pxl8.sh
3
pxl8.sh
|
|
@ -378,7 +378,7 @@ case "$COMMAND" in
|
|||
|
||||
EXECUTABLE="$BINDIR/pxl8"
|
||||
|
||||
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"
|
||||
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/microui/src/microui.c lib/miniz/miniz.c"
|
||||
|
||||
PXL8_SOURCE_FILES="
|
||||
src/pxl8.c
|
||||
|
|
@ -392,6 +392,7 @@ case "$COMMAND" in
|
|||
src/pxl8_script.c
|
||||
src/pxl8_tilemap.c
|
||||
src/pxl8_tilesheet.c
|
||||
src/pxl8_ui.c
|
||||
src/pxl8_vfx.c
|
||||
"
|
||||
|
||||
|
|
|
|||
|
|
@ -329,5 +329,37 @@ end
|
|||
|
||||
pxl8.gfx = gfx
|
||||
pxl8.input = input
|
||||
pxl8.ui = _pxl8_ui
|
||||
|
||||
function pxl8.bounds(x, y, w, h)
|
||||
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
|
||||
end
|
||||
|
||||
function pxl8.ui_window_begin(title, x, y, w, h, options)
|
||||
local rect = ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
|
||||
return C.pxl8_ui_window_begin(_pxl8_ui, title, rect, options or 0)
|
||||
end
|
||||
|
||||
function pxl8.ui_window_end()
|
||||
C.pxl8_ui_window_end(_pxl8_ui)
|
||||
end
|
||||
|
||||
function pxl8.ui_button(label)
|
||||
return C.pxl8_ui_button(_pxl8_ui, label)
|
||||
end
|
||||
|
||||
function pxl8.ui_label(text)
|
||||
C.pxl8_ui_label(_pxl8_ui, text)
|
||||
end
|
||||
|
||||
function pxl8.ui_layout_row(item_count, widths, height)
|
||||
local widths_array = widths
|
||||
if type(widths) == "table" then
|
||||
widths_array = ffi.new("int[?]", #widths, widths)
|
||||
elseif type(widths) == "number" then
|
||||
widths_array = ffi.new("int[1]", widths)
|
||||
end
|
||||
C.pxl8_ui_layout_row(_pxl8_ui, item_count, widths_array, height)
|
||||
end
|
||||
|
||||
return pxl8
|
||||
|
|
|
|||
106
src/pxl8.c
106
src/pxl8.c
|
|
@ -18,6 +18,7 @@
|
|||
#include "pxl8_macros.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "pxl8_ui.h"
|
||||
|
||||
#define PXL8_MAX_REPL_COMMANDS 4096
|
||||
|
||||
|
|
@ -41,7 +42,9 @@ typedef struct pxl8_state {
|
|||
pxl8_repl_state repl;
|
||||
pxl8_resolution resolution;
|
||||
pxl8_script* script;
|
||||
pxl8_ui* ui;
|
||||
|
||||
f32 current_fps;
|
||||
f32 fps_timer;
|
||||
i32 frame_count;
|
||||
u64 last_time;
|
||||
|
|
@ -50,6 +53,7 @@ typedef struct pxl8_state {
|
|||
bool repl_mode;
|
||||
bool running;
|
||||
bool script_loaded;
|
||||
bool show_fps;
|
||||
char script_path[256];
|
||||
|
||||
pxl8_input_state input;
|
||||
|
|
@ -246,6 +250,13 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
|||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
app.ui = pxl8_ui_create(app.gfx);
|
||||
if (!app.ui) {
|
||||
pxl8_error("Failed to create UI");
|
||||
pxl8_gfx_destroy(app.gfx);
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
app.script = pxl8_script_create();
|
||||
if (!app.script) {
|
||||
pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(app.script));
|
||||
|
|
@ -283,6 +294,7 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
|||
|
||||
pxl8_script_set_gfx(app.script, app.gfx);
|
||||
pxl8_script_set_input(app.script, &app.input);
|
||||
pxl8_script_set_ui(app.script, app.ui);
|
||||
|
||||
if (app.script_path[0] != '\0') {
|
||||
pxl8_result result = pxl8_script_load_main(app.script, app.script_path);
|
||||
|
|
@ -317,11 +329,8 @@ SDL_AppResult SDL_AppIterate(void* appstate) {
|
|||
app->last_time = current_time;
|
||||
app->time += dt;
|
||||
|
||||
if (app->fps_timer >= 3.0f) {
|
||||
if (!app->repl_mode) {
|
||||
f32 avg_fps = app->frame_count / app->fps_timer;
|
||||
pxl8_info("FPS: %.1f", avg_fps);
|
||||
}
|
||||
if (app->fps_timer >= 1.0f) {
|
||||
app->current_fps = app->frame_count / app->fps_timer;
|
||||
app->frame_count = 0;
|
||||
app->fps_timer = 0.0f;
|
||||
}
|
||||
|
|
@ -339,6 +348,29 @@ SDL_AppResult SDL_AppIterate(void* appstate) {
|
|||
}
|
||||
}
|
||||
|
||||
if (app->ui) {
|
||||
pxl8_ui_frame_begin(app->ui);
|
||||
|
||||
for (i32 i = 0; i < 3; i++) {
|
||||
if (app->input.mouse_buttons[i] && app->input.mouse_buttons_pressed[i]) {
|
||||
pxl8_ui_input_mousedown(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1);
|
||||
}
|
||||
if (!app->input.mouse_buttons[i]) {
|
||||
pxl8_ui_input_mouseup(app->ui, app->input.mouse_x, app->input.mouse_y, i + 1);
|
||||
}
|
||||
}
|
||||
pxl8_ui_input_mousemove(app->ui, app->input.mouse_x, app->input.mouse_y);
|
||||
|
||||
for (i32 key = 0; key < 256; key++) {
|
||||
if (app->input.keys_pressed[key]) {
|
||||
pxl8_ui_input_keydown(app->ui, key);
|
||||
}
|
||||
if (!app->input.keys[key] && app->input.keys_pressed[key]) {
|
||||
pxl8_ui_input_keyup(app->ui, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (app->script_loaded) {
|
||||
pxl8_script_call_function_f32(app->script, "update", dt);
|
||||
|
||||
|
|
@ -363,18 +395,25 @@ SDL_AppResult SDL_AppIterate(void* appstate) {
|
|||
i32 render_width, render_height;
|
||||
pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height);
|
||||
|
||||
f32 scale = fminf(bounds.w / (f32)render_width, bounds.h / (f32)render_height);
|
||||
i32 scaled_width = (i32)(render_width * scale);
|
||||
i32 scaled_height = (i32)(render_height * scale);
|
||||
i32 offset_x = (bounds.w - scaled_width) / 2;
|
||||
i32 offset_y = (bounds.h - scaled_height) / 2;
|
||||
if (app->show_fps && app->current_fps > 0.0f) {
|
||||
char fps_text[32];
|
||||
SDL_snprintf(fps_text, sizeof(fps_text), "FPS: %d", (i32)(app->current_fps + 0.5f));
|
||||
pxl8_text(app->gfx, fps_text, render_width - 80, 10, 15);
|
||||
}
|
||||
|
||||
pxl8_gfx_viewport(app->gfx, offset_x, offset_y, scaled_width, scaled_height);
|
||||
if (app->ui) {
|
||||
pxl8_ui_frame_end(app->ui);
|
||||
}
|
||||
|
||||
pxl8_gfx_set_viewport(app->gfx, pxl8_gfx_viewport(bounds, render_width, render_height));
|
||||
pxl8_gfx_upload_framebuffer(app->gfx);
|
||||
pxl8_gfx_upload_atlas(app->gfx);
|
||||
pxl8_gfx_present(app->gfx);
|
||||
|
||||
SDL_memset(app->input.keys_pressed, 0, sizeof(app->input.keys_pressed));
|
||||
SDL_memset(app->input.mouse_buttons_pressed, 0, sizeof(app->input.mouse_buttons_pressed));
|
||||
app->input.mouse_wheel_x = 0;
|
||||
app->input.mouse_wheel_y = 0;
|
||||
|
||||
return app->running ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
|
||||
}
|
||||
|
|
@ -393,6 +432,10 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
|||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
|
||||
if (event->key.key == SDLK_F3) {
|
||||
app->show_fps = !app->show_fps;
|
||||
}
|
||||
|
||||
SDL_Keycode key = event->key.key;
|
||||
if (key < 256) {
|
||||
if (!app->input.keys[key]) {
|
||||
|
|
@ -411,6 +454,44 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
if (!app->input.mouse_buttons[button]) {
|
||||
app->input.mouse_buttons_pressed[button] = true;
|
||||
}
|
||||
app->input.mouse_buttons[button] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
app->input.mouse_buttons[button] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION: {
|
||||
i32 window_mouse_x = (i32)event->motion.x;
|
||||
i32 window_mouse_y = (i32)event->motion.y;
|
||||
|
||||
i32 render_width, render_height;
|
||||
pxl8_gfx_get_resolution_dimensions(app->resolution, &render_width, &render_height);
|
||||
pxl8_viewport vp = pxl8_gfx_viewport(pxl8_gfx_get_bounds(app->gfx), render_width, render_height);
|
||||
|
||||
app->input.mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
|
||||
app->input.mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_WHEEL: {
|
||||
app->input.mouse_wheel_x = (i32)event->wheel.x;
|
||||
app->input.mouse_wheel_y = (i32)event->wheel.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
|
|
@ -431,6 +512,9 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
|||
app->cart = NULL;
|
||||
}
|
||||
pxl8_script_destroy(app->script);
|
||||
if (app->ui) {
|
||||
pxl8_ui_destroy(app->ui);
|
||||
}
|
||||
pxl8_gfx_destroy(app->gfx);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,14 +31,14 @@ pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32*
|
|||
|
||||
for (i32 y = 0; y < glyph->height && y < font->default_height; y++) {
|
||||
for (i32 x = 0; x < glyph->width && x < font->default_width; x++) {
|
||||
i32 glyph_idx = y * 8 + x;
|
||||
i32 atlas_idx = (atlas_y + y) * (*atlas_width) + (atlas_x + x);
|
||||
|
||||
if (glyph->format == PXL8_FONT_FORMAT_INDEXED) {
|
||||
u8 pixel_byte = glyph->data.indexed[glyph_idx / 8];
|
||||
u8 pixel_bit = (pixel_byte >> (7 - (glyph_idx % 8))) & 1;
|
||||
u8 pixel_byte = glyph->data.indexed[y];
|
||||
u8 pixel_bit = (pixel_byte >> x) & 1;
|
||||
(*atlas_data)[atlas_idx] = pixel_bit ? 255 : 0;
|
||||
} else {
|
||||
i32 glyph_idx = y * 8 + x;
|
||||
u32 rgba_pixel = glyph->data.rgba[glyph_idx];
|
||||
(*atlas_data)[atlas_idx] = (rgba_pixel >> 24) & 0xFF;
|
||||
}
|
||||
|
|
|
|||
312
src/pxl8_gfx.c
312
src/pxl8_gfx.c
|
|
@ -6,11 +6,37 @@
|
|||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_blit.h"
|
||||
#include "pxl8_font.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
||||
*r = color & 0xFF;
|
||||
*g = (color >> 8) & 0xFF;
|
||||
*b = (color >> 16) & 0xFF;
|
||||
*a = (color >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_color_pack(u8 r, u8 g, u8 b, u8 a) {
|
||||
return r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) {
|
||||
return c1 + (i32)((c2 - c1) * t);
|
||||
}
|
||||
|
||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
|
||||
pxl8_viewport vp = {0};
|
||||
vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height);
|
||||
vp.scaled_width = (i32)(width * vp.scale);
|
||||
vp.scaled_height = (i32)(height * vp.scale);
|
||||
vp.offset_x = (bounds.w - vp.scaled_width) / 2;
|
||||
vp.offset_y = (bounds.h - vp.scaled_height) / 2;
|
||||
return vp;
|
||||
}
|
||||
|
||||
typedef struct pxl8_atlas_entry {
|
||||
char path[256];
|
||||
u32 sprite_id;
|
||||
|
|
@ -44,10 +70,7 @@ struct pxl8_gfx {
|
|||
u32 sprite_frame_width;
|
||||
u32 sprite_frames_per_row;
|
||||
|
||||
i32 viewport_height;
|
||||
i32 viewport_width;
|
||||
i32 viewport_x;
|
||||
i32 viewport_y;
|
||||
pxl8_viewport viewport;
|
||||
|
||||
bool backface_culling;
|
||||
pxl8_mat4 model;
|
||||
|
|
@ -191,10 +214,11 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons
|
|||
}
|
||||
}
|
||||
|
||||
gfx->viewport_x = 0;
|
||||
gfx->viewport_y = 0;
|
||||
gfx->viewport_width = gfx->framebuffer_width;
|
||||
gfx->viewport_height = gfx->framebuffer_height;
|
||||
gfx->viewport.offset_x = 0;
|
||||
gfx->viewport.offset_y = 0;
|
||||
gfx->viewport.scaled_width = gfx->framebuffer_width;
|
||||
gfx->viewport.scaled_height = gfx->framebuffer_height;
|
||||
gfx->viewport.scale = 1.0f;
|
||||
|
||||
gfx->backface_culling = true;
|
||||
gfx->model = pxl8_mat4_identity();
|
||||
|
|
@ -241,7 +265,6 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
|||
SDL_free(gfx);
|
||||
}
|
||||
|
||||
// resource loading
|
||||
pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height) {
|
||||
if (!gfx || !gfx->initialized) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
|
|
@ -417,17 +440,10 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
|||
return PXL8_OK;
|
||||
}
|
||||
|
||||
// rendering pipeline
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return;
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer,
|
||||
gfx->framebuffer_width * 4);
|
||||
} else {
|
||||
static void pxl8_upload_indexed_texture(SDL_Texture* texture, const u8* indexed, const u32* palette, u32 palette_size, i32 width, i32 height, u32 default_color) {
|
||||
static u32* rgba_buffer = NULL;
|
||||
static size_t buffer_size = 0;
|
||||
size_t needed_size = gfx->framebuffer_width * gfx->framebuffer_height;
|
||||
size_t needed_size = width * height;
|
||||
|
||||
if (buffer_size < needed_size) {
|
||||
rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4);
|
||||
|
|
@ -436,13 +452,25 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
|||
|
||||
if (!rgba_buffer) return;
|
||||
|
||||
for (i32 i = 0; i < gfx->framebuffer_width * gfx->framebuffer_height; i++) {
|
||||
u8 index = gfx->framebuffer[i];
|
||||
rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0xFF000000;
|
||||
for (i32 i = 0; i < width * height; i++) {
|
||||
u8 index = indexed[i];
|
||||
rgba_buffer[i] = (index < palette_size) ? palette[index] : default_color;
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, rgba_buffer,
|
||||
SDL_UpdateTexture(texture, NULL, rgba_buffer, width * 4);
|
||||
}
|
||||
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return;
|
||||
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer,
|
||||
gfx->framebuffer_width * 4);
|
||||
} else {
|
||||
pxl8_upload_indexed_texture(gfx->framebuffer_texture, gfx->framebuffer,
|
||||
gfx->palette, gfx->palette_size,
|
||||
gfx->framebuffer_width, gfx->framebuffer_height,
|
||||
0xFF000000);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -453,17 +481,10 @@ void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
|||
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas,
|
||||
gfx->sprite_atlas_width * 4);
|
||||
} else {
|
||||
u32* rgba_buffer = (u32*)SDL_malloc(gfx->sprite_atlas_width * gfx->sprite_atlas_height * 4);
|
||||
if (!rgba_buffer) return;
|
||||
|
||||
for (u32 i = 0; i < gfx->sprite_atlas_width * gfx->sprite_atlas_height; i++) {
|
||||
u8 index = gfx->atlas[i];
|
||||
rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0x00000000;
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, rgba_buffer,
|
||||
gfx->sprite_atlas_width * 4);
|
||||
SDL_free(rgba_buffer);
|
||||
pxl8_upload_indexed_texture(gfx->sprite_atlas_texture, gfx->atlas,
|
||||
gfx->palette, gfx->palette_size,
|
||||
gfx->sprite_atlas_width, gfx->sprite_atlas_height,
|
||||
0x00000000);
|
||||
}
|
||||
|
||||
gfx->atlas_dirty = false;
|
||||
|
|
@ -483,19 +504,15 @@ void pxl8_gfx_present(pxl8_gfx* gfx) {
|
|||
SDL_RenderPresent(gfx->renderer);
|
||||
}
|
||||
|
||||
void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height) {
|
||||
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) {
|
||||
if (!gfx) return;
|
||||
gfx->viewport_x = x;
|
||||
gfx->viewport_y = y;
|
||||
gfx->viewport_width = width;
|
||||
gfx->viewport_height = height;
|
||||
gfx->viewport = vp;
|
||||
}
|
||||
|
||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
|
||||
(void)gfx; (void)left; (void)right; (void)top; (void)bottom;
|
||||
}
|
||||
|
||||
// drawing primitives
|
||||
void pxl8_clr(pxl8_gfx* gfx, u32 color) {
|
||||
if (!gfx || !gfx->framebuffer) return;
|
||||
|
||||
|
|
@ -571,12 +588,26 @@ void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
|||
pxl8_line(gfx, x, y + h - 1, x, y, color);
|
||||
}
|
||||
|
||||
void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
||||
if (!gfx) return;
|
||||
static inline void pxl8_pixel_unchecked(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
||||
i32 idx = y * gfx->framebuffer_width + x;
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
((u32*)gfx->framebuffer)[idx] = color;
|
||||
} else {
|
||||
gfx->framebuffer[idx] = color & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
for (i32 py = y; py < y + h; py++) {
|
||||
for (i32 px = x; px < x + w; px++) {
|
||||
pxl8_pixel(gfx, px, py, color);
|
||||
void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
||||
if (!gfx || !gfx->framebuffer) return;
|
||||
|
||||
i32 x0 = (x < 0) ? 0 : x;
|
||||
i32 y0 = (y < 0) ? 0 : y;
|
||||
i32 x1 = (x + w > gfx->framebuffer_width) ? gfx->framebuffer_width : x + w;
|
||||
i32 y1 = (y + h > gfx->framebuffer_height) ? gfx->framebuffer_height : y + h;
|
||||
|
||||
for (i32 py = y0; py < y1; py++) {
|
||||
for (i32 px = x0; px < x1; px++) {
|
||||
pxl8_pixel_unchecked(gfx, px, py, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -609,20 +640,65 @@ void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
|||
}
|
||||
|
||||
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
||||
if (!gfx) return;
|
||||
if (!gfx || !gfx->framebuffer) return;
|
||||
|
||||
for (i32 y = -radius; y <= radius; y++) {
|
||||
for (i32 x = -radius; x <= radius; x++) {
|
||||
i32 x0 = (cx - radius < 0) ? -cx : -radius;
|
||||
i32 y0 = (cy - radius < 0) ? -cy : -radius;
|
||||
i32 x1 = (cx + radius >= gfx->framebuffer_width) ? gfx->framebuffer_width - cx - 1 : radius;
|
||||
i32 y1 = (cy + radius >= gfx->framebuffer_height) ? gfx->framebuffer_height - cy - 1 : radius;
|
||||
|
||||
for (i32 y = y0; y <= y1; y++) {
|
||||
for (i32 x = x0; x <= x1; x++) {
|
||||
if (x * x + y * y <= radius * radius) {
|
||||
pxl8_pixel(gfx, cx + x, cy + y, color);
|
||||
pxl8_pixel_unchecked(gfx, cx + x, cy + y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
||||
if (!gfx || !text) return;
|
||||
(void)x; (void)y; (void)color;
|
||||
if (!gfx || !text || !gfx->framebuffer) return;
|
||||
|
||||
const pxl8_font* font = &pxl8_default_font;
|
||||
i32 cursor_x = x;
|
||||
i32 cursor_y = y;
|
||||
|
||||
for (const char* c = text; *c; c++) {
|
||||
const pxl8_glyph* glyph = pxl8_font_find_glyph(font, (u32)*c);
|
||||
if (!glyph) continue;
|
||||
|
||||
for (i32 gy = 0; gy < glyph->height; gy++) {
|
||||
for (i32 gx = 0; gx < glyph->width; gx++) {
|
||||
i32 px = cursor_x + gx;
|
||||
i32 py = cursor_y + gy;
|
||||
|
||||
if (px < 0 || px >= gfx->framebuffer_width ||
|
||||
py < 0 || py >= gfx->framebuffer_height) continue;
|
||||
|
||||
u8 pixel_bit = 0;
|
||||
|
||||
if (glyph->format == PXL8_FONT_FORMAT_INDEXED) {
|
||||
u8 pixel_byte = glyph->data.indexed[gy];
|
||||
pixel_bit = (pixel_byte >> gx) & 1;
|
||||
} else {
|
||||
i32 glyph_idx = gy * 8 + gx;
|
||||
u32 rgba_pixel = glyph->data.rgba[glyph_idx];
|
||||
pixel_bit = ((rgba_pixel >> 24) & 0xFF) > 128 ? 1 : 0;
|
||||
}
|
||||
|
||||
if (pixel_bit) {
|
||||
i32 fb_idx = py * gfx->framebuffer_width + px;
|
||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
||||
((u32*)gfx->framebuffer)[fb_idx] = color;
|
||||
} else {
|
||||
gfx->framebuffer[fb_idx] = (u8)color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor_x += font->default_width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -681,7 +757,6 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
|||
}
|
||||
}
|
||||
|
||||
// palette effects
|
||||
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step) {
|
||||
if (!gfx || !gfx->palette || count == 0) return;
|
||||
|
||||
|
|
@ -711,24 +786,19 @@ void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 ta
|
|||
if (amount < 0.0f) amount = 0.0f;
|
||||
if (amount > 1.0f) amount = 1.0f;
|
||||
|
||||
u8 target_r = target_color & 0xFF;
|
||||
u8 target_g = (target_color >> 8) & 0xFF;
|
||||
u8 target_b = (target_color >> 16) & 0xFF;
|
||||
u8 target_a = (target_color >> 24) & 0xFF;
|
||||
u8 target_r, target_g, target_b, target_a;
|
||||
pxl8_color_unpack(target_color, &target_r, &target_g, &target_b, &target_a);
|
||||
|
||||
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
||||
u32 current = gfx->palette[start + i];
|
||||
u8 cur_r = current & 0xFF;
|
||||
u8 cur_g = (current >> 8) & 0xFF;
|
||||
u8 cur_b = (current >> 16) & 0xFF;
|
||||
u8 cur_a = (current >> 24) & 0xFF;
|
||||
u8 cur_r, cur_g, cur_b, cur_a;
|
||||
pxl8_color_unpack(gfx->palette[start + i], &cur_r, &cur_g, &cur_b, &cur_a);
|
||||
|
||||
u8 new_r = cur_r + (i32)((target_r - cur_r) * amount);
|
||||
u8 new_g = cur_g + (i32)((target_g - cur_g) * amount);
|
||||
u8 new_b = cur_b + (i32)((target_b - cur_b) * amount);
|
||||
u8 new_a = cur_a + (i32)((target_a - cur_a) * amount);
|
||||
u8 new_r = pxl8_color_lerp_channel(cur_r, target_r, amount);
|
||||
u8 new_g = pxl8_color_lerp_channel(cur_g, target_g, amount);
|
||||
u8 new_b = pxl8_color_lerp_channel(cur_b, target_b, amount);
|
||||
u8 new_a = pxl8_color_lerp_channel(cur_a, target_a, amount);
|
||||
|
||||
gfx->palette[start + i] = new_r | (new_g << 8) | (new_b << 16) | (new_a << 24);
|
||||
gfx->palette[start + i] = pxl8_color_pack(new_r, new_g, new_b, new_a);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -739,50 +809,36 @@ void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2,
|
|||
if (t > 1.0f) t = 1.0f;
|
||||
|
||||
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
||||
u32 col1 = palette1[i];
|
||||
u32 col2 = palette2[i];
|
||||
u8 r1, g1, b1, a1, r2, g2, b2, a2;
|
||||
pxl8_color_unpack(palette1[i], &r1, &g1, &b1, &a1);
|
||||
pxl8_color_unpack(palette2[i], &r2, &g2, &b2, &a2);
|
||||
|
||||
u8 r1 = col1 & 0xFF;
|
||||
u8 g1 = (col1 >> 8) & 0xFF;
|
||||
u8 b1 = (col1 >> 16) & 0xFF;
|
||||
u8 a1 = (col1 >> 24) & 0xFF;
|
||||
u8 r = pxl8_color_lerp_channel(r1, r2, t);
|
||||
u8 g = pxl8_color_lerp_channel(g1, g2, t);
|
||||
u8 b = pxl8_color_lerp_channel(b1, b2, t);
|
||||
u8 a = pxl8_color_lerp_channel(a1, a2, t);
|
||||
|
||||
u8 r2 = col2 & 0xFF;
|
||||
u8 g2 = (col2 >> 8) & 0xFF;
|
||||
u8 b2 = (col2 >> 16) & 0xFF;
|
||||
u8 a2 = (col2 >> 24) & 0xFF;
|
||||
|
||||
u8 r = r1 + (i32)((r2 - r1) * t);
|
||||
u8 g = g1 + (i32)((g2 - g1) * t);
|
||||
u8 b = b1 + (i32)((b2 - b1) * t);
|
||||
u8 a = a1 + (i32)((a2 - a1) * t);
|
||||
|
||||
gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24);
|
||||
gfx->palette[start + i] = pxl8_color_pack(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color) {
|
||||
if (!gfx || !gfx->palette || count <= 1) return;
|
||||
|
||||
u8 from_r = from_color & 0xFF;
|
||||
u8 from_g = (from_color >> 8) & 0xFF;
|
||||
u8 from_b = (from_color >> 16) & 0xFF;
|
||||
u8 from_a = (from_color >> 24) & 0xFF;
|
||||
|
||||
u8 to_r = to_color & 0xFF;
|
||||
u8 to_g = (to_color >> 8) & 0xFF;
|
||||
u8 to_b = (to_color >> 16) & 0xFF;
|
||||
u8 to_a = (to_color >> 24) & 0xFF;
|
||||
u8 from_r, from_g, from_b, from_a;
|
||||
u8 to_r, to_g, to_b, to_a;
|
||||
pxl8_color_unpack(from_color, &from_r, &from_g, &from_b, &from_a);
|
||||
pxl8_color_unpack(to_color, &to_r, &to_g, &to_b, &to_a);
|
||||
|
||||
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
||||
f32 t = (f32)i / (f32)(count - 1);
|
||||
|
||||
u8 r = from_r + (i32)((to_r - from_r) * t);
|
||||
u8 g = from_g + (i32)((to_g - from_g) * t);
|
||||
u8 b = from_b + (i32)((to_b - from_b) * t);
|
||||
u8 a = from_a + (i32)((to_a - from_a) * t);
|
||||
u8 r = pxl8_color_lerp_channel(from_r, to_r, t);
|
||||
u8 g = pxl8_color_lerp_channel(from_g, to_g, t);
|
||||
u8 b = pxl8_color_lerp_channel(from_b, to_b, t);
|
||||
u8 a = pxl8_color_lerp_channel(from_a, to_a, t);
|
||||
|
||||
gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24);
|
||||
gfx->palette[start + i] = pxl8_color_pack(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -895,27 +951,8 @@ void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color)
|
|||
pxl8_line(gfx, x0, y0, x1, y1, color);
|
||||
}
|
||||
|
||||
static void pxl8_draw_flat_bottom_triangle(
|
||||
pxl8_gfx* gfx,
|
||||
i32 x0, i32 y0, f32 z0,
|
||||
i32 x1, i32 y1, f32 z1,
|
||||
i32 x2, i32 y2, f32 z2,
|
||||
u32 color
|
||||
) {
|
||||
(void)z2;
|
||||
if (y1 == y0) return;
|
||||
|
||||
f32 inv_slope_1 = (f32)(x1 - x0) / (f32)(y1 - y0);
|
||||
f32 inv_slope_2 = (f32)(x2 - x0) / (f32)(y2 - y0);
|
||||
|
||||
f32 cur_x1 = (f32)x0;
|
||||
f32 cur_x2 = (f32)x0;
|
||||
|
||||
for (i32 y = y0; y <= y1; y++) {
|
||||
if (y >= 0 && y < gfx->framebuffer_height) {
|
||||
i32 xs = (i32)cur_x1;
|
||||
i32 xe = (i32)cur_x2;
|
||||
|
||||
static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) {
|
||||
if (y < 0 || y >= gfx->framebuffer_height) return;
|
||||
if (xs > xe) {
|
||||
i32 tmp = xs; xs = xe; xe = tmp;
|
||||
}
|
||||
|
|
@ -934,6 +971,24 @@ static void pxl8_draw_flat_bottom_triangle(
|
|||
}
|
||||
}
|
||||
|
||||
static void pxl8_draw_flat_bottom_triangle(
|
||||
pxl8_gfx* gfx,
|
||||
i32 x0, i32 y0, f32 z0,
|
||||
i32 x1, i32 y1, f32 z1,
|
||||
i32 x2, i32 y2, f32 z2,
|
||||
u32 color
|
||||
) {
|
||||
(void)z2;
|
||||
if (y1 == y0) return;
|
||||
|
||||
f32 inv_slope_1 = (f32)(x1 - x0) / (f32)(y1 - y0);
|
||||
f32 inv_slope_2 = (f32)(x2 - x0) / (f32)(y2 - y0);
|
||||
|
||||
f32 cur_x1 = (f32)x0;
|
||||
f32 cur_x2 = (f32)x0;
|
||||
|
||||
for (i32 y = y0; y <= y1; y++) {
|
||||
pxl8_fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color);
|
||||
cur_x1 += inv_slope_1;
|
||||
cur_x2 += inv_slope_2;
|
||||
}
|
||||
|
|
@ -956,28 +1011,7 @@ static void pxl8_draw_flat_top_triangle(
|
|||
f32 cur_x2 = (f32)x2;
|
||||
|
||||
for (i32 y = y2; y > y0; y--) {
|
||||
if (y >= 0 && y < gfx->framebuffer_height) {
|
||||
i32 xs = (i32)cur_x1;
|
||||
i32 xe = (i32)cur_x2;
|
||||
|
||||
if (xs > xe) {
|
||||
i32 tmp = xs; xs = xe; xe = tmp;
|
||||
}
|
||||
|
||||
for (i32 x = xs; x <= xe; x++) {
|
||||
if (x >= 0 && x < gfx->framebuffer_width) {
|
||||
f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs);
|
||||
f32 z = z0 + t * (z1 - z0);
|
||||
|
||||
i32 idx = y * gfx->zbuffer_width + x;
|
||||
if (z <= gfx->zbuffer[idx]) {
|
||||
gfx->zbuffer[idx] = z;
|
||||
pxl8_pixel(gfx, x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color);
|
||||
cur_x1 -= inv_slope_1;
|
||||
cur_x2 -= inv_slope_2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@ void pxl8_gfx_present(pxl8_gfx* gfx);
|
|||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
||||
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height);
|
||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
||||
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
|
||||
|
||||
void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color);
|
||||
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step);
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@
|
|||
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_ui.h"
|
||||
#include "pxl8_vfx.h"
|
||||
|
||||
struct pxl8_script {
|
||||
lua_State* L;
|
||||
pxl8_gfx* gfx;
|
||||
pxl8_input_state* input;
|
||||
pxl8_ui* ui;
|
||||
char last_error[1024];
|
||||
char main_path[256];
|
||||
char watch_dir[256];
|
||||
|
|
@ -35,7 +37,7 @@ static const char* pxl8_ffi_cdefs =
|
|||
"typedef float f32;\n"
|
||||
"typedef double f64;\n"
|
||||
"typedef struct pxl8_gfx pxl8_gfx;\n"
|
||||
"typedef struct { int x, y, w, h; } pxl8_rect;\n"
|
||||
"typedef struct { int x, y, w, h; } pxl8_bounds;\n"
|
||||
"typedef struct { int x, y; } pxl8_point;\n"
|
||||
"\n"
|
||||
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
|
||||
|
|
@ -145,7 +147,30 @@ static const char* pxl8_ffi_cdefs =
|
|||
"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n"
|
||||
"pxl8_mat4 pxl8_mat4_rotate_z(float angle);\n"
|
||||
"pxl8_mat4 pxl8_mat4_scale(float x, float y, float z);\n"
|
||||
"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n";
|
||||
"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_ui pxl8_ui;\n"
|
||||
"typedef struct { unsigned char bg_color; unsigned int sprite_id; int corner_size; int edge_size; int padding; } pxl8_frame_theme;\n"
|
||||
"typedef struct { bool enabled; const char* label; } pxl8_menu_item;\n"
|
||||
"pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx);\n"
|
||||
"void pxl8_ui_destroy(pxl8_ui* ui);\n"
|
||||
"void pxl8_ui_frame_begin(pxl8_ui* ui);\n"
|
||||
"void pxl8_ui_frame_end(pxl8_ui* ui);\n"
|
||||
"void pxl8_ui_input_keydown(pxl8_ui* ui, int key);\n"
|
||||
"void pxl8_ui_input_keyup(pxl8_ui* ui, int key);\n"
|
||||
"void pxl8_ui_input_mousedown(pxl8_ui* ui, int x, int y, int button);\n"
|
||||
"void pxl8_ui_input_mousemove(pxl8_ui* ui, int x, int y);\n"
|
||||
"void pxl8_ui_input_mouseup(pxl8_ui* ui, int x, int y, int button);\n"
|
||||
"void pxl8_ui_input_scroll(pxl8_ui* ui, int x, int y);\n"
|
||||
"void pxl8_ui_input_text(pxl8_ui* ui, const char* text);\n"
|
||||
"bool pxl8_ui_button(pxl8_ui* ui, const char* label);\n"
|
||||
"void pxl8_ui_label(pxl8_ui* ui, const char* text);\n"
|
||||
"void pxl8_ui_layout_row(pxl8_ui* ui, int item_count, const int* widths, int height);\n"
|
||||
"int pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, int item_count);\n"
|
||||
"void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme);\n"
|
||||
"bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, int options);\n"
|
||||
"void pxl8_ui_window_end(pxl8_ui* ui);\n"
|
||||
"pxl8_frame_theme pxl8_ui_theme_default(void);\n";
|
||||
|
||||
void pxl8_lua_info(const char* msg) {
|
||||
pxl8_info("%s", msg);
|
||||
|
|
@ -249,12 +274,19 @@ void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input) {
|
|||
}
|
||||
}
|
||||
|
||||
pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
|
||||
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
|
||||
void pxl8_script_set_ui(pxl8_script* script, pxl8_ui* ui) {
|
||||
if (!script) return;
|
||||
script->ui = ui;
|
||||
if (script->L && ui) {
|
||||
lua_pushlightuserdata(script->L, ui);
|
||||
lua_setglobal(script->L, "_pxl8_ui");
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char** out_basename) {
|
||||
char* filename_copy = strdup(filename);
|
||||
char* last_slash = strrchr(filename_copy, '/');
|
||||
const char* basename = filename;
|
||||
*out_basename = (char*)filename;
|
||||
|
||||
if (last_slash) {
|
||||
*last_slash = '\0';
|
||||
|
|
@ -263,12 +295,22 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
|
|||
if (script_dir && original_cwd) {
|
||||
chdir(script_dir);
|
||||
pxl8_script_set_cart_path(script, script_dir, original_cwd);
|
||||
basename = last_slash + 1;
|
||||
*out_basename = strdup(last_slash + 1);
|
||||
}
|
||||
free(script_dir);
|
||||
free(original_cwd);
|
||||
}
|
||||
|
||||
free(filename_copy);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
|
||||
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
char* basename;
|
||||
pxl8_script_prepare_path(script, filename, &basename);
|
||||
|
||||
pxl8_result result = PXL8_OK;
|
||||
if (luaL_dofile(script->L, basename) != 0) {
|
||||
pxl8_script_set_error(script, lua_tostring(script->L, -1));
|
||||
|
|
@ -278,8 +320,7 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
|
|||
script->last_error[0] = '\0';
|
||||
}
|
||||
|
||||
free(filename_copy);
|
||||
|
||||
if (basename != filename) free(basename);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -320,28 +361,14 @@ void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const
|
|||
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename) {
|
||||
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
char* filename_copy = strdup(filename);
|
||||
char* last_slash = strrchr(filename_copy, '/');
|
||||
const char* basename = filename;
|
||||
|
||||
if (last_slash) {
|
||||
*last_slash = '\0';
|
||||
char* script_dir = realpath(filename_copy, NULL);
|
||||
char* original_cwd = getcwd(NULL, 0);
|
||||
if (script_dir && original_cwd) {
|
||||
chdir(script_dir);
|
||||
pxl8_script_set_cart_path(script, script_dir, original_cwd);
|
||||
basename = last_slash + 1;
|
||||
}
|
||||
free(script_dir);
|
||||
free(original_cwd);
|
||||
}
|
||||
char* basename;
|
||||
pxl8_script_prepare_path(script, filename, &basename);
|
||||
|
||||
lua_getglobal(script->L, "fennel");
|
||||
if (lua_isnil(script->L, -1)) {
|
||||
pxl8_script_set_error(script, "Fennel not loaded");
|
||||
lua_pop(script->L, 1);
|
||||
free(filename_copy);
|
||||
if (basename != filename) free(basename);
|
||||
return PXL8_ERROR_SCRIPT_ERROR;
|
||||
}
|
||||
|
||||
|
|
@ -358,7 +385,7 @@ pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filenam
|
|||
}
|
||||
|
||||
lua_pop(script->L, 1);
|
||||
free(filename_copy);
|
||||
if (basename != filename) free(basename);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "pxl8_ui.h"
|
||||
|
||||
typedef struct pxl8_script pxl8_script;
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ const char* pxl8_script_get_last_error(pxl8_script* script);
|
|||
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd);
|
||||
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx);
|
||||
void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input);
|
||||
void pxl8_script_set_ui(pxl8_script* script, pxl8_ui* ui);
|
||||
|
||||
pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name);
|
||||
pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg);
|
||||
|
|
|
|||
|
|
@ -66,8 +66,21 @@ typedef struct pxl8_bounds {
|
|||
typedef struct pxl8_input_state {
|
||||
bool keys[256];
|
||||
bool keys_pressed[256];
|
||||
|
||||
bool mouse_buttons[3];
|
||||
bool mouse_buttons_pressed[3];
|
||||
i32 mouse_wheel_x;
|
||||
i32 mouse_wheel_y;
|
||||
i32 mouse_x;
|
||||
i32 mouse_y;
|
||||
} pxl8_input_state;
|
||||
|
||||
typedef struct pxl8_point {
|
||||
i32 x, y;
|
||||
} pxl8_point;
|
||||
|
||||
typedef struct pxl8_viewport {
|
||||
i32 offset_x, offset_y;
|
||||
i32 scaled_width, scaled_height;
|
||||
f32 scale;
|
||||
} pxl8_viewport;
|
||||
|
|
|
|||
216
src/pxl8_ui.c
Normal file
216
src/pxl8_ui.c
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
#include "pxl8_ui.h"
|
||||
#include "../lib/microui/src/microui.h"
|
||||
#include "pxl8_font.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct pxl8_ui {
|
||||
pxl8_gfx* gfx;
|
||||
mu_Context mu_ctx;
|
||||
i32 font_height;
|
||||
i32 font_width;
|
||||
};
|
||||
|
||||
static int mu_text_width(mu_Font font, const char* str, int len) {
|
||||
const pxl8_font* f = (const pxl8_font*)font;
|
||||
if (!f) return 0;
|
||||
|
||||
if (len < 0) {
|
||||
len = (int)strlen(str);
|
||||
}
|
||||
|
||||
i32 width = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
const pxl8_glyph* glyph = pxl8_font_find_glyph(f, (u32)str[i]);
|
||||
if (glyph) {
|
||||
width += f->default_width;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
static int mu_text_height(mu_Font font) {
|
||||
const pxl8_font* f = (const pxl8_font*)font;
|
||||
if (!f) return 8;
|
||||
return f->default_height;
|
||||
}
|
||||
|
||||
static void pxl8_ui_render_commands(pxl8_ui* ui) {
|
||||
mu_Command* cmd = NULL;
|
||||
while (mu_next_command(&ui->mu_ctx, &cmd)) {
|
||||
switch (cmd->type) {
|
||||
case MU_COMMAND_RECT: {
|
||||
mu_RectCommand* rc = (mu_RectCommand*)cmd;
|
||||
u8 color = rc->color.r;
|
||||
pxl8_rect_fill(ui->gfx, rc->rect.x, rc->rect.y, rc->rect.w, rc->rect.h, color);
|
||||
break;
|
||||
}
|
||||
case MU_COMMAND_TEXT: {
|
||||
mu_TextCommand* tc = (mu_TextCommand*)cmd;
|
||||
u8 color = tc->color.r;
|
||||
pxl8_text(ui->gfx, tc->str, tc->pos.x, tc->pos.y, color);
|
||||
break;
|
||||
}
|
||||
case MU_COMMAND_CLIP: {
|
||||
break;
|
||||
}
|
||||
case MU_COMMAND_ICON: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pxl8_ui_draw_9slice(pxl8_gfx* gfx, pxl8_bounds rect, pxl8_frame_theme* theme) {
|
||||
if (theme->sprite_id == 0) return;
|
||||
|
||||
i32 cs = theme->corner_size;
|
||||
i32 es = theme->edge_size;
|
||||
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x, rect.y, cs, cs);
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x + rect.w - cs, rect.y, cs, cs);
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x, rect.y + rect.h - cs, cs, cs);
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x + rect.w - cs, rect.y + rect.h - cs, cs, cs);
|
||||
|
||||
for (i32 x = cs; x < rect.w - cs; x += es) {
|
||||
i32 w = (x + es > rect.w - cs) ? (rect.w - cs - x) : es;
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x + x, rect.y, w, es);
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x + x, rect.y + rect.h - es, w, es);
|
||||
}
|
||||
|
||||
for (i32 y = cs; y < rect.h - cs; y += es) {
|
||||
i32 h = (y + es > rect.h - cs) ? (rect.h - cs - y) : es;
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x, rect.y + y, es, h);
|
||||
pxl8_sprite(gfx, theme->sprite_id, rect.x + rect.w - es, rect.y + y, es, h);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx) {
|
||||
if (!gfx) return NULL;
|
||||
|
||||
pxl8_ui* ui = (pxl8_ui*)malloc(sizeof(pxl8_ui));
|
||||
if (!ui) return NULL;
|
||||
|
||||
memset(ui, 0, sizeof(pxl8_ui));
|
||||
ui->gfx = gfx;
|
||||
ui->font_width = 8;
|
||||
ui->font_height = 8;
|
||||
|
||||
mu_init(&ui->mu_ctx);
|
||||
ui->mu_ctx.style->font = (mu_Font)&pxl8_default_font;
|
||||
ui->mu_ctx.text_height = mu_text_height;
|
||||
ui->mu_ctx.text_width = mu_text_width;
|
||||
|
||||
return ui;
|
||||
}
|
||||
|
||||
void pxl8_ui_destroy(pxl8_ui* ui) {
|
||||
if (!ui) return;
|
||||
free(ui);
|
||||
}
|
||||
|
||||
void pxl8_ui_frame_begin(pxl8_ui* ui) {
|
||||
if (!ui) return;
|
||||
mu_begin(&ui->mu_ctx);
|
||||
}
|
||||
|
||||
void pxl8_ui_frame_end(pxl8_ui* ui) {
|
||||
if (!ui) return;
|
||||
mu_end(&ui->mu_ctx);
|
||||
pxl8_ui_render_commands(ui);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_keydown(pxl8_ui* ui, i32 key) {
|
||||
if (!ui) return;
|
||||
mu_input_keydown(&ui->mu_ctx, key);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_keyup(pxl8_ui* ui, i32 key) {
|
||||
if (!ui) return;
|
||||
mu_input_keyup(&ui->mu_ctx, key);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_mousedown(pxl8_ui* ui, i32 x, i32 y, i32 button) {
|
||||
if (!ui) return;
|
||||
mu_input_mousedown(&ui->mu_ctx, x, y, button);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_mousemove(pxl8_ui* ui, i32 x, i32 y) {
|
||||
if (!ui) return;
|
||||
mu_input_mousemove(&ui->mu_ctx, x, y);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_mouseup(pxl8_ui* ui, i32 x, i32 y, i32 button) {
|
||||
if (!ui) return;
|
||||
mu_input_mouseup(&ui->mu_ctx, x, y, button);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_scroll(pxl8_ui* ui, i32 x, i32 y) {
|
||||
if (!ui) return;
|
||||
mu_input_scroll(&ui->mu_ctx, x, y);
|
||||
}
|
||||
|
||||
void pxl8_ui_input_text(pxl8_ui* ui, const char* text) {
|
||||
if (!ui || !text) return;
|
||||
mu_input_text(&ui->mu_ctx, text);
|
||||
}
|
||||
|
||||
bool pxl8_ui_button(pxl8_ui* ui, const char* label) {
|
||||
if (!ui) return false;
|
||||
return mu_button(&ui->mu_ctx, label) & MU_RES_SUBMIT;
|
||||
}
|
||||
|
||||
void pxl8_ui_label(pxl8_ui* ui, const char* text) {
|
||||
if (!ui || !text) return;
|
||||
mu_label(&ui->mu_ctx, text);
|
||||
}
|
||||
|
||||
void pxl8_ui_layout_row(pxl8_ui* ui, i32 item_count, const i32* widths, i32 height) {
|
||||
if (!ui) return;
|
||||
mu_layout_row(&ui->mu_ctx, item_count, widths, height);
|
||||
}
|
||||
|
||||
i32 pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, i32 item_count) {
|
||||
if (!ui || !items) return -1;
|
||||
|
||||
i32 selected = -1;
|
||||
for (i32 i = 0; i < item_count; i++) {
|
||||
if (!items[i].enabled) {
|
||||
mu_label(&ui->mu_ctx, items[i].label);
|
||||
} else if (mu_button(&ui->mu_ctx, items[i].label) & MU_RES_SUBMIT) {
|
||||
selected = i;
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme) {
|
||||
if (!ui || !theme) return;
|
||||
|
||||
pxl8_rect_fill(ui->gfx, rect.x, rect.y, rect.w, rect.h, theme->bg_color);
|
||||
pxl8_ui_draw_9slice(ui->gfx, rect, theme);
|
||||
}
|
||||
|
||||
bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, i32 options) {
|
||||
if (!ui) return false;
|
||||
mu_Rect r = { rect.x, rect.y, rect.w, rect.h };
|
||||
return mu_begin_window_ex(&ui->mu_ctx, title, r, options);
|
||||
}
|
||||
|
||||
void pxl8_ui_window_end(pxl8_ui* ui) {
|
||||
if (!ui) return;
|
||||
mu_end_window(&ui->mu_ctx);
|
||||
}
|
||||
|
||||
pxl8_frame_theme pxl8_ui_theme_default(void) {
|
||||
pxl8_frame_theme theme = {0};
|
||||
theme.bg_color = 0;
|
||||
theme.corner_size = 8;
|
||||
theme.edge_size = 8;
|
||||
theme.padding = 4;
|
||||
theme.sprite_id = 0;
|
||||
return theme;
|
||||
}
|
||||
58
src/pxl8_ui.h
Normal file
58
src/pxl8_ui.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_ui pxl8_ui;
|
||||
|
||||
typedef struct pxl8_frame_theme {
|
||||
u8 bg_color;
|
||||
u32 sprite_id;
|
||||
i32 corner_size;
|
||||
i32 edge_size;
|
||||
i32 padding;
|
||||
} pxl8_frame_theme;
|
||||
|
||||
typedef struct pxl8_menu_item {
|
||||
bool enabled;
|
||||
const char* label;
|
||||
} pxl8_menu_item;
|
||||
|
||||
typedef enum pxl8_ui_options {
|
||||
PXL8_UI_NOCLOSE = 1 << 0,
|
||||
PXL8_UI_NOFRAME = 1 << 1,
|
||||
PXL8_UI_NORESIZE = 1 << 2,
|
||||
PXL8_UI_NOTITLE = 1 << 3
|
||||
} pxl8_ui_options;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_ui* pxl8_ui_create(pxl8_gfx* gfx);
|
||||
void pxl8_ui_destroy(pxl8_ui* ui);
|
||||
|
||||
void pxl8_ui_frame_begin(pxl8_ui* ui);
|
||||
void pxl8_ui_frame_end(pxl8_ui* ui);
|
||||
|
||||
void pxl8_ui_input_keydown(pxl8_ui* ui, i32 key);
|
||||
void pxl8_ui_input_keyup(pxl8_ui* ui, i32 key);
|
||||
void pxl8_ui_input_mousedown(pxl8_ui* ui, i32 x, i32 y, i32 button);
|
||||
void pxl8_ui_input_mousemove(pxl8_ui* ui, i32 x, i32 y);
|
||||
void pxl8_ui_input_mouseup(pxl8_ui* ui, i32 x, i32 y, i32 button);
|
||||
void pxl8_ui_input_scroll(pxl8_ui* ui, i32 x, i32 y);
|
||||
void pxl8_ui_input_text(pxl8_ui* ui, const char* text);
|
||||
|
||||
bool pxl8_ui_button(pxl8_ui* ui, const char* label);
|
||||
void pxl8_ui_label(pxl8_ui* ui, const char* text);
|
||||
void pxl8_ui_layout_row(pxl8_ui* ui, i32 item_count, const i32* widths, i32 height);
|
||||
i32 pxl8_ui_menu(pxl8_ui* ui, pxl8_menu_item* items, i32 item_count);
|
||||
void pxl8_ui_panel(pxl8_ui* ui, pxl8_bounds rect, pxl8_frame_theme* theme);
|
||||
bool pxl8_ui_window_begin(pxl8_ui* ui, const char* title, pxl8_bounds rect, i32 options);
|
||||
void pxl8_ui_window_end(pxl8_ui* ui);
|
||||
|
||||
pxl8_frame_theme pxl8_ui_theme_default(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue