add ui module

This commit is contained in:
asrael 2025-10-04 11:55:04 -05:00
parent 1744e689b5
commit 6008ebf5ed
12 changed files with 703 additions and 215 deletions

20
demo/ui_demo.fnl Normal file
View 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)))))

View file

@ -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
"

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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,6 +951,26 @@ 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 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;
}
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);
}
}
}
}
static void pxl8_draw_flat_bottom_triangle(
pxl8_gfx* gfx,
i32 x0, i32 y0, f32 z0,
@ -912,28 +988,7 @@ static void pxl8_draw_flat_bottom_triangle(
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;
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;
}
@ -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;
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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
View 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
View 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