#include "pxl8_ui.h" #include "../lib/microui/src/microui.h" #include "pxl8_font.h" #include #include #include 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; }