diff --git a/GEMINI.md b/GEMINI.md index 539c863..e5cbdae 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -60,8 +60,9 @@ Here is a breakdown of the next steps to advance the `attempt_1` implementation * The tape and annotations are currently lost when the program closes. * Move away from purely transient `VirtualAlloc` buffers to a memory-mapped file approach (or a manual Save/Load equivalent in WinAPI) to allow the "executable as source" to persist between sessions. -4. **Refine Visual Editor Interactions:** +4. ~~**Refine Visual Editor Interactions:**~~ (Completed via `microui` integration) * Implement a proper internal text-editing cursor within the `STag_Data` and `STag_Format` (annotation) tokens, rather than relying on backspace-truncation and appendage. + * Migrated to `microui` for immediate mode GUI floating panels, auto-layout token sizing (for a natural text look), and window resizing. 5. **Continuous Validation & Complex Control Flow:** * Expand the primitive set to allow for more complex, AST-less control flow (e.g., handling Lambdas or specific Basic Block jumps). diff --git a/attempt_1/duffle.amd64.win32.h b/attempt_1/duffle.amd64.win32.h index 646037e..5eaf6b5 100644 --- a/attempt_1/duffle.amd64.win32.h +++ b/attempt_1/duffle.amd64.win32.h @@ -676,6 +676,11 @@ WinAPI void* ms_create_font_a( U4 iPitchAndFamily, char const* pszFaceName ) asm("CreateFontA"); +WinAPI void* ms_create_compatible_dc(void* hdc) asm("CreateCompatibleDC"); +WinAPI void* ms_create_compatible_bitmap(void* hdc, S4 cx, S4 cy) asm("CreateCompatibleBitmap"); +WinAPI B4 ms_bit_blt(void* hdcDest, S4 x, S4 y, S4 w, S4 h, void* hdcSrc, S4 xSrc, S4 ySrc, U4 rop) asm("BitBlt"); +WinAPI B4 ms_delete_dc(void* hdc) asm("DeleteDC"); +WinAPI B4 ms_get_client_rect(void* hwnd, MS_RECT* lpRect) asm("GetClientRect"); WinAPI void* ms_select_object(void* hdc, void* h) asm("SelectObject"); WinAPI S4 ms_rectangle(void* hdc, S4 left, S4 top, S4 right, S4 bottom) asm("Rectangle"); WinAPI S4 ms_set_bk_mode(void* hdc, S4 mode) asm("SetBkMode"); @@ -685,9 +690,21 @@ WinAPI S4 ms_delete_object(void* ho) asm(" #define MS_MEM_COMMIT 0x00001000 #define MS_MEM_RESERVE 0x00002000 #define MS_PAGE_READWRITE 0x04 +#define MS_SRCCOPY 0x00CC0020 #define MS_WM_DESTROY 0x0002 +#define MS_WM_SIZE 0x0005 #define MS_WM_PAINT 0x000F +#define MS_WM_ERASEBKGND 0x0014 #define MS_WM_KEYDOWN 0x0100 +#define MS_WM_KEYUP 0x0101 +#define MS_WM_MOUSEMOVE 0x0200 +#define MS_WM_LBUTTONDOWN 0x0201 +#define MS_WM_LBUTTONUP 0x0202 +#define MS_WM_RBUTTONDOWN 0x0204 +#define MS_WM_RBUTTONUP 0x0205 +#define MS_WM_MBUTTONDOWN 0x0207 +#define MS_WM_MBUTTONUP 0x0208 +#define MS_WM_MOUSEWHEEL 0x020A #define MS_WS_OVERLAPPEDWINDOW 0x00CF0000 #define MS_WS_VISIBLE 0x10000000 #define MS_VK_LEFT 0x25 diff --git a/attempt_1/main.c b/attempt_1/main.c index 18b5ed3..f59345d 100644 --- a/attempt_1/main.c +++ b/attempt_1/main.c @@ -465,13 +465,76 @@ IA_ void compile_and_run_tape(void) debug_log(str8("JIT finished. RAX: , RDX: "), ktl_str8_from_arr(post_jit_log_table)); } + +#undef r +#undef v +#undef expect +#include "microui.c" +#undef expect +#define expect(x,y) __builtin_expect(x, y) // so compiler knows the common path +#define r restrict +#define v volatile + +global mu_Context mu_ctx; + +internal int text_width_cb(mu_Font font, const char *str, int len) { + if (len == -1) { len = 0; while (str[len]) len++; } + return len * 11; // Approx 11px per char for Consolas 20 +} + +internal int text_height_cb(mu_Font font) { + return 20; // Consolas 20 height +} + +internal void gdi_draw_rect(void* hdc, mu_Rect rect, mu_Color color) { + U1 red = ((U1*)&color)[0]; + U1 green = ((U1*)&color)[1]; + U1 blue = ((U1*)&color)[2]; + void* hBrush = ms_create_solid_brush((red) | (green << 8) | (blue << 16)); + void* hOldBrush = ms_select_object(hdc, hBrush); + ms_rectangle(hdc, rect.x - 1, rect.y - 1, rect.x + rect.w + 1, rect.y + rect.h + 1); + ms_select_object(hdc, hOldBrush); + ms_delete_object(hBrush); +} + +internal void render_microui(void* hdc) { + mu_Command *cmd = NULL; + while (mu_next_command(&mu_ctx, &cmd)) { + switch (cmd->type) { + case MU_COMMAND_TEXT: { + U1 red = ((U1*)&cmd->text.color)[0]; + U1 green = ((U1*)&cmd->text.color)[1]; + U1 blue = ((U1*)&cmd->text.color)[2]; + ms_set_text_color(hdc, (red) | (green << 8) | (blue << 16)); + int len = 0; while (cmd->text.str[len]) len++; + ms_text_out_a(hdc, cmd->text.pos.x, cmd->text.pos.y, cmd->text.str, len); + break; + } + case MU_COMMAND_RECT: { + gdi_draw_rect(hdc, cmd->rect.rect, cmd->rect.color); + break; + } + case MU_COMMAND_ICON: { + gdi_draw_rect(hdc, cmd->icon.rect, cmd->icon.color); + break; + } + case MU_COMMAND_CLIP: { + break; + } + } + } +} + S8 win_proc(void* hwnd, U4 msg, U8 wparam, S8 lparam) { U8 tape_count = tape_arena.used / sizeof(U4); U4*r tape_ptr = u4_r(tape_arena.start); switch (msg) { case MS_WM_CHAR: { - if (editor_mode != MODE_EDIT) return 0; + char buf[2] = { (char)wparam, 0 }; + mu_input_text(&mu_ctx, buf); + + if (editor_mode != MODE_EDIT) { ms_invalidate_rect(hwnd, nullptr, true); return 0; } U4 t = tape_ptr[cursor_idx]; U4 tag = unpack_tag(t); @@ -479,7 +542,7 @@ S8 win_proc(void* hwnd, U4 msg, U8 wparam, S8 lparam) U1 c = u1_(wparam); B4 should_skip = c < 32 || (c == 'e' && mode_switch_now); - if (should_skip) { mode_switch_now = false; return 0; } + if (should_skip) { mode_switch_now = false; ms_invalidate_rect(hwnd, nullptr, true); return 0; } if (tag == STag_Data) { U4 digit = 16; @@ -512,7 +575,34 @@ S8 win_proc(void* hwnd, U4 msg, U8 wparam, S8 lparam) ms_invalidate_rect(hwnd, nullptr, true); return 0; } + case MS_WM_MOUSEMOVE: { + mu_input_mousemove(&mu_ctx, lparam & 0xFFFF, (lparam >> 16) & 0xFFFF); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_LBUTTONDOWN: { + mu_input_mousedown(&mu_ctx, lparam & 0xFFFF, (lparam >> 16) & 0xFFFF, MU_MOUSE_LEFT); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_LBUTTONUP: { + mu_input_mouseup(&mu_ctx, lparam & 0xFFFF, (lparam >> 16) & 0xFFFF, MU_MOUSE_LEFT); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_MOUSEWHEEL: { + mu_input_scroll(&mu_ctx, 0, ((S4)(wparam >> 16)) / -30); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } case MS_WM_KEYDOWN: { + int key = 0; + if (wparam == MS_VK_BACK) key = MU_KEY_BACKSPACE; + if (wparam == MS_VK_RETURN) key = MU_KEY_RETURN; + if (wparam == 0x10) key = MU_KEY_SHIFT; + if (wparam == 0x11) key = MU_KEY_CTRL; + if (wparam == 0x12) key = MU_KEY_ALT; + if (key) mu_input_keydown(&mu_ctx, key); if (wparam == 0x45 && editor_mode == MODE_NAV) { editor_mode = MODE_EDIT; @@ -650,115 +740,122 @@ S8 win_proc(void* hwnd, U4 msg, U8 wparam, S8 lparam) ms_invalidate_rect(hwnd, nullptr, true); return 0; } + case MS_WM_KEYUP: { + int key = 0; + if (wparam == MS_VK_BACK) key = MU_KEY_BACKSPACE; + if (wparam == MS_VK_RETURN) key = MU_KEY_RETURN; + if (wparam == 0x10) key = MU_KEY_SHIFT; + if (wparam == 0x11) key = MU_KEY_CTRL; + if (wparam == 0x12) key = MU_KEY_ALT; + if (key) mu_input_keyup(&mu_ctx, key); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_SIZE: { + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_ERASEBKGND: { + return 1; + } case MS_WM_PAINT: { - MS_PAINTSTRUCT ps; - void* hdc = ms_begin_paint(hwnd, & ps); - void* hFont = ms_create_font_a(20, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, "Consolas"); - void* hOldFont = ms_select_object(hdc, hFont); + mu_begin(&mu_ctx); - ms_set_bk_mode(hdc, 1); + if (mu_begin_window(&mu_ctx, "ColorForth Source Tape", mu_rect(10, 10, 900, 480))) { + U4*r tape_ptr = u4_r(tape_arena.start); + U8*r anno_ptr = u8_r(anno_arena.start); - void* hBgBrush = ms_create_solid_brush(0x00222222); - ms_select_object(hdc, hBgBrush); - ms_rectangle(hdc, -1, -1, 3000, 3000); + S4 start_x = 5, start_y = 5, spacing_x = 6, spacing_y = 26; + S4 x = start_x, y = start_y; - void* hBrushEdit = ms_create_solid_brush(0x008E563B); - void* hBrushNav = ms_create_solid_brush(0x00262F3B); + for (U8 i = 0; i < tape_count; i++) { + U4 t = tape_ptr[i]; + U4 tag = unpack_tag(t); + U4 val = unpack_val(t); + U8 anno = anno_ptr[i]; - S4 start_x = 40, start_y = 60, spacing_x = 110, spacing_y = 35; - S4 x = start_x, y = start_y; + if (tag == STag_Format && val == 0xA) { + x = start_x; + y += spacing_y; + continue; + } - U4*r tape_ptr = u4_r(tape_arena.start); - U8*r anno_ptr = u8_r(anno_arena.start); + U4 color_u32 = tag_colors[tag]; + const char* prefix = tag_prefixes[tag]; + + char val_str[9]; + if (tag == STag_Data) { + u64_to_hex(val, val_str, 6); + val_str[6] = '\0'; + } + else + { + char* a_str = (char*) & anno; + for(int c=0; c<8; c++) { + val_str[c] = a_str[c] ? a_str[c] : ' '; + } + val_str[8] = '\0'; + } + char out_buf[12]; + out_buf[0] = prefix[0]; + out_buf[1] = ' '; + mem_copy(u8_(out_buf + 2), u8_(val_str), 8); + out_buf[10] = '\0'; - for (U8 i = 0; i < tape_count; i++) - { - if (x >= start_x + (TOKENS_PER_ROW * spacing_x)) { - x = start_x; y += spacing_y; - } - S4 render_y = y - scroll_y_offset; - if (i == cursor_idx && render_y >= 30 && render_y < 500) { - ms_select_object(hdc, editor_mode == MODE_EDIT ? hBrushEdit : hBrushNav); - ms_rectangle(hdc, x - 5, render_y - 2, x + 95, render_y + 22); - } - if (render_y >= 30 && render_y < 500) - { - U4 t = tape_ptr[i]; - U4 tag = unpack_tag(t); - U4 val = unpack_val(t); - U8 anno = anno_ptr[i]; - if (tag == STag_Format && val == 0xA) { - ms_set_text_color(hdc, 0x00444444); - ms_text_out_a(hdc, x, render_y, " \\n ", 6); - x = start_x; - y += spacing_y; - } - else - { - U4 color = tag_colors[tag]; - const char* prefix = tag_prefixes[tag]; - - ms_set_text_color(hdc, color); - if (editor_mode == MODE_EDIT && i == cursor_idx) { - ms_set_text_color(hdc, 0x001E1E1E); - } - - char val_str[9]; - if (tag == STag_Data) { - u64_to_hex(val, val_str, 6); - val_str[6] = '\0'; - } - else - { - char* a_str = (char*) & anno; - for(int c=0; c<8; c++) { - val_str[c] = a_str[c] ? a_str[c] : ' '; - } - val_str[8] = '\0'; - } - char out_buf[12]; - out_buf[0] = prefix[0]; - out_buf[1] = ' '; - mem_copy(u8_(out_buf + 2), u8_(val_str), 8); - out_buf[10] = '\0'; - - ms_text_out_a(hdc, x, render_y, out_buf, 10); - x += spacing_x; - } - } - else if (unpack_tag(tape_ptr[i]) == STag_Format && unpack_val(tape_ptr[i]) == 0xA) { - x = start_x; - y += spacing_y; - } - else { - x += spacing_x; - } - } + int btn_w = 20 + text_width_cb(NULL, out_buf, 10); + + // auto-wrap + mu_Container* current_window = mu_get_current_container(&mu_ctx); + if (x + btn_w > current_window->body.w - 15) { + x = start_x; + y += spacing_y; + } - void* hHudBrush = ms_create_solid_brush(0x00141E23); - ms_select_object(hdc, hHudBrush); - ms_rectangle(hdc, -1, 500, 3000, 3000); - ms_rectangle(hdc, -1, -1, 3000, 40); + mu_ctx.style->colors[MU_COLOR_BUTTON] = mu_color(color_u32 & 0xFF, (color_u32 >> 8) & 0xFF, (color_u32 >> 16) & 0xFF, 255); + + if (i == cursor_idx && editor_mode == MODE_EDIT) { + mu_ctx.style->colors[MU_COLOR_TEXT] = mu_color(0,0,0,255); + mu_ctx.style->colors[MU_COLOR_BUTTON] = mu_color(0x8E, 0x56, 0x3B, 255); + } else if (i == cursor_idx && editor_mode == MODE_NAV) { + mu_ctx.style->colors[MU_COLOR_BUTTON] = mu_color(0x26, 0x2F, 0x3B, 255); + } + + mu_layout_set_next(&mu_ctx, mu_rect(x, y, btn_w, 22), 1); + + if (mu_button(&mu_ctx, out_buf)) { + cursor_idx = i; + editor_mode = MODE_NAV; + } - ms_set_text_color(hdc, 0x00AAAAAA); - ms_text_out_a(hdc, 40, 10, "x86-64 Machine Code Emitter | 2-Reg Stack | [F5] Toggle Run Mode | [PgUp/PgDn] Scroll", 85); - - ms_set_text_color(hdc, 0x00FFFFFF); - char jit_str[64] = "Mode: Incremental | JIT Size: 0x000 bytes"; + x += btn_w + spacing_x; + + mu_ctx.style->colors[MU_COLOR_BUTTON] = mu_color(75, 75, 75, 255); + mu_ctx.style->colors[MU_COLOR_TEXT] = mu_color(230, 230, 230, 255); + } + + // Dummy element to ensure scrolling bounds are correct + mu_layout_set_next(&mu_ctx, mu_rect(start_x, y + spacing_y, 10, 10), 1); + mu_draw_rect(&mu_ctx, mu_layout_next(&mu_ctx), mu_color(0,0,0,0)); + + mu_end_window(&mu_ctx); + } + + if (mu_begin_window(&mu_ctx, "HUD / Memory", mu_rect(10, 500, 900, 200))) { + char jit_str[64] = "Mode: Incremental | JIT Size: 0x000 bytes"; if (run_full) mem_copy(u8_(jit_str + 6), u8_("Full "), 11); u64_to_hex(code_arena.used, jit_str + 32, 3); - ms_text_out_a(hdc, 40, 520, jit_str, 41); - - char state_str[64] = "RAX: 00000000 | RDX: 00000000"; + mu_layout_row(&mu_ctx, 1, (int[]){-1}, 0); + mu_text(&mu_ctx, "x86-64 Machine Code Emitter | 2-Reg Stack | [F5] Toggle Run Mode | [PgUp/PgDn] Scroll"); + mu_text(&mu_ctx, jit_str); + + char state_str[64] = "RAX: 00000000 | RDX: 00000000"; u64_to_hex(vm_rax, state_str + 5, 8); u64_to_hex(vm_rdx, state_str + 21, 8); - ms_set_text_color(hdc, 0x0094BAA1); - ms_text_out_a(hdc, 40, 550, state_str, 29); - - if (tape_count > 0 && cursor_idx < tape_count) { + mu_text(&mu_ctx, state_str); + + if (tape_count > 0 && cursor_idx < tape_count) { U4 cur_tag = unpack_tag(tape_ptr[cursor_idx]); const char* tag_name = tag_names [cur_tag]; - U4 cur_color = tag_colors[cur_tag]; char semantics_str[64] = "Current Tag: "; U4 name_len = 0; while (tag_name[name_len]) { @@ -766,47 +863,48 @@ S8 win_proc(void* hwnd, U4 msg, U8 wparam, S8 lparam) name_len ++; } semantics_str[13 + name_len] = '\0'; - ms_set_text_color(hdc, cur_color); - ms_text_out_a(hdc, 40, 580, semantics_str, 13 + name_len); - } - - ms_set_text_color(hdc, 0x00C8C8C8); - ms_text_out_a(hdc, 400, 520, "Global Memory (Contiguous Array):", 33); - for (int i=0; i < 4; i ++) { - char glob_str[32] = "[0]: 00000000"; - glob_str[1] = '0' + i; - u64_to_hex(vm_globals[i], glob_str + 5, 8); - ms_set_text_color(hdc, 0x00D6A454); - ms_text_out_a(hdc, 400, 550 + (i * 25), glob_str, 13); - } - - ms_set_text_color(hdc, 0x00C8C8C8); - ms_text_out_a(hdc, 750, 520, "Print Log:", 10); - for (int i = 0; i> 28) & 0x0F) +#define unpack_val(token) ( (token) & 0x0FFFFFFF) + +#define TOKENS_PER_ROW 8 + +#define MODE_NAV 0 +#define MODE_EDIT 1 + +global FArena tape_arena; +global FArena anno_arena; +global U8 cursor_idx = 0; +global U4 editor_mode = MODE_NAV; +global B4 mode_switch_now = false; + +global FArena code_arena; + +global U8 vm_rax = 0; +global U8 vm_rdx = 0; +global U8 vm_globals[16] = {0}; + +global B4 run_full = false; +global U8 log_buffer[16] = {0}; +global U4 log_count = 0; +global S4 scroll_y_offset = 0; + +// New GDI log +#define GDI_LOG_MAX_LINES 10 +#define GDI_LOG_MAX_LINE_LEN 128 +global char gdi_log_buffer[GDI_LOG_MAX_LINES][GDI_LOG_MAX_LINE_LEN] = {0}; +global U4 gdi_log_count = 0; + +internal void debug_log(Str8 fmt, KTL_Str8 table) { + // A static buffer for our log lines. + LP_ UTF8 console_log_buffer[1024]; + mem_zero(u8_(console_log_buffer), 1024); + + // Format the string. + Str8 result = str8_fmt_ktl_buf(slice_ut_arr(console_log_buffer), table, fmt); + + // Also write to our GDI log buffer + if (gdi_log_count < GDI_LOG_MAX_LINES) { + U4 len_to_copy = result.len < GDI_LOG_MAX_LINE_LEN - 1 ? result.len : GDI_LOG_MAX_LINE_LEN - 1; + mem_copy(u8_(gdi_log_buffer[gdi_log_count]), u8_(result.ptr), len_to_copy); + gdi_log_buffer[gdi_log_count][len_to_copy] = '\0'; + gdi_log_count++; + } + + // Get stdout handle. + MS_Handle stdout_handle = ms_get_std_handle(MS_STD_OUTPUT); + + // Write the formatted string. + ms_write_console(stdout_handle, result.ptr, (U4)result.len, nullptr, 0); + + // Write a newline. + ms_write_console(stdout_handle, (UTF8 const*r)"\n", 1, nullptr, 0); +} + +U8 ms_builtin_print(U8 val, U8 rdx_val, U8 r8_val, U8 r9_val) { + char hex1[9], hex2[9], hex3[9], hex4[9]; + u64_to_hex(val, hex1, 8); hex1[8] = '\0'; + u64_to_hex(rdx_val, hex2, 8); hex2[8] = '\0'; + u64_to_hex(r8_val, hex3, 8); hex3[8] = '\0'; + u64_to_hex(r9_val, hex4, 8); hex4[8] = '\0'; + + KTL_Slot_Str8 log_table[] = { + { ktl_str8_key("v1"), str8(hex1) }, + { ktl_str8_key("v2"), str8(hex2) }, + { ktl_str8_key("v3"), str8(hex3) }, + { ktl_str8_key("v4"), str8(hex4) }, + }; + debug_log(str8("FFI PRINT -> RCX: RDX: R8: R9:"), ktl_str8_from_arr(log_table)); + if (log_count < 16) log_buffer[log_count++] = val; + return val; +} + +// Visual Linker & O(1) Dictionary +global U4 tape_to_code_offset[65536] = {0}; + +// --- WinAPI Persistence --- +#define MS_GENERIC_READ 0x80000000 +#define MS_GENERIC_WRITE 0x40000000 +#define MS_CREATE_ALWAYS 2 +#define MS_OPEN_EXISTING 3 +#define MS_FILE_ATTRIBUTE_NORMAL 0x80 +#define MS_VK_F1 0x70 +#define MS_VK_F2 0x71 + +WinAPI void* ms_create_file_a(char const* lpFileName, U4 dwDesiredAccess, U4 dwShareMode, void* lpSecurityAttributes, U4 dwCreationDisposition, U4 dwFlagsAndAttributes, void* hTemplateFile) asm("CreateFileA"); +WinAPI B4 ms_write_file(void* hFile, void const* lpBuffer, U4 nNumberOfBytesToWrite, U4* lpNumberOfBytesWritten, void* lpOverlapped) asm("WriteFile"); +WinAPI B4 ms_read_file(void* hFile, void* lpBuffer, U4 nNumberOfBytesToRead, U4* lpNumberOfBytesRead, void* lpOverlapped) asm("ReadFile"); +WinAPI B4 ms_close_handle(void* hObject) asm("CloseHandle"); + +#define PRIM_SWAP 1 +#define PRIM_MULT 2 +#define PRIM_ADD 3 +#define PRIM_FETCH 4 +#define PRIM_DEC 5 +#define PRIM_STORE 6 +#define PRIM_RET_Z 7 +#define PRIM_RET 8 +#define PRIM_PRINT 9 +#define PRIM_RET_S 10 +#define PRIM_DUP 11 +#define PRIM_DROP 12 +#define PRIM_SUB 13 + +global const char* prim_names[] = { + "", + "SWAP ", + "MULT ", + "ADD ", + "FETCH ", + "DEC ", + "STORE ", + "RET_IF_Z", + "RETURN ", + "PRINT ", + "RET_IF_S", + "DUP ", + "DROP ", + "SUB " +}; + +internal U4 resolve_name_to_index(const char* ref_name); +internal void relink_tape(void); + +IA_ void compile_and_run_tape(void); + +internal void save_cartridge(void) { + void* hFile = ms_create_file_a("cartridge.bin", MS_GENERIC_WRITE, 0, nullptr, MS_CREATE_ALWAYS, MS_FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != (void*)-1) { + U4 written = 0; + ms_write_file(hFile, & tape_arena.used, 8, & written, nullptr); + ms_write_file(hFile, & anno_arena.used, 8, & written, nullptr); + ms_write_file(hFile, & cursor_idx, 8, & written, nullptr); + ms_write_file(hFile, (void*)tape_arena.start, (U4)tape_arena.used, & written, nullptr); + ms_write_file(hFile, (void*)anno_arena.start, (U4)anno_arena.used, & written, nullptr); + ms_close_handle(hFile); + } +} + +internal void load_cartridge(void) { + void* hFile = ms_create_file_a("cartridge.bin", MS_GENERIC_READ, 0, nullptr, MS_OPEN_EXISTING, MS_FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile != (void*)-1) { + U4 read = 0; + ms_read_file(hFile, & tape_arena.used, 8, & read, nullptr); + ms_read_file(hFile, & anno_arena.used, 8, & read, nullptr); + ms_read_file(hFile, & cursor_idx, 8, & read, nullptr); + ms_read_file(hFile, (void*)tape_arena.start, (U4)tape_arena.used, & read, nullptr); + ms_read_file(hFile, (void*)anno_arena.start, (U4)anno_arena.used, & read, nullptr); + ms_close_handle(hFile); + relink_tape(); + compile_and_run_tape(); + } +} + +IA_ void scatter(U4 token, const char* anno_str) { + if (tape_arena.used + sizeof(U4) <= tape_arena.capacity && anno_arena.used + sizeof(U8) <= anno_arena.capacity) { + U4 tag = unpack_tag(token); + U4 val = unpack_val(token); + + if (anno_str && (tag == STag_Call || tag == STag_Imm)) { + val = resolve_name_to_index(anno_str); + } + + U4*r ptr = u4_r(tape_arena.start + tape_arena.used); + ptr[0] = pack_token(tag, val); + tape_arena.used += sizeof(U4); + U8*r aptr = u8_r(anno_arena.start + anno_arena.used); + aptr[0] = 0; + if (anno_str) { + char* dest = (char*)aptr; + int i = 0; while(i < 8 && anno_str[i]) { dest[i] = anno_str[i]; i ++; } + } + anno_arena.used += sizeof(U8); + } +} + +internal void emit8(U1 b) { + if (code_arena.used + 1 <= code_arena.capacity) { + u1_r(code_arena.start + code_arena.used)[0] = b; + code_arena.used += 1; + } +} +internal void emit32(U4 val) { + if (code_arena.used + 4 <= code_arena.capacity) { + u4_r(code_arena.start + code_arena.used)[0] = val; + code_arena.used += 4; + } +} + +internal void pad32(void) { + while ((code_arena.used % 4) != 0) emit8(0x90); +} + +internal U4 resolve_name_to_index(const char* ref_name) { + U8 tape_count = tape_arena.used / sizeof(U4); + U4*r tape_ptr = u4_r(tape_arena.start); + U8*r anno_ptr = u8_r(anno_arena.start); + + U8 prim_count = array_len(prim_names); + for (int p = 1; p < prim_count; p++) { + int match = 1; + for (int c = 0; c < 8; c++) { + char c1 = ref_name[c] ? ref_name[c] : ' '; + char c2 = prim_names[p][c] ? prim_names[p][c] : ' '; + if (c1 != c2) { match = 0; break; } + } + if (match) return p + 0x10000; + } + + for (U8 j = 0; j < tape_count; j++) { + if (unpack_tag(tape_ptr[j]) == STag_Define) { + char* def_name = (char*)&anno_ptr[j]; + int match = 1; + for (int c = 0; c < 8; c++) { + char c1 = ref_name[c] ? ref_name[c] : ' '; + char c2 = def_name[c] ? def_name[c] : ' '; + if (c1 != c2) { match = 0; break; } + } + if (match) return j; + } + } + return 0; +} + +internal void relink_tape(void) { + U8 tape_count = tape_arena.used / sizeof(U4); + U4*r tape_ptr = u4_r(tape_arena.start); + U8*r anno_ptr = u8_r(anno_arena.start); + + for (U8 i = 0; i < tape_count; i++) { + U4 t = tape_ptr[i]; + U4 tag = unpack_tag(t); + if (tag == STag_Call || tag == STag_Imm) { + char* ref_name = (char*)&anno_ptr[i]; + U4 new_val = resolve_name_to_index(ref_name); + tape_ptr[i] = pack_token(tag, new_val); + } + } +} + +internal void compile_action(U4 val) +{ + if (val >= 0x10000) { + U4 p = val - 0x10000; + if (p == PRIM_SWAP) { + emit8(0x48); emit8(0x87); emit8(0xC2); + pad32(); + return; + } else if (p == PRIM_MULT) { + emit8(0x48); emit8(0x0F); emit8(0xAF); emit8(0xC2); + pad32(); + return; + } else if (p == PRIM_ADD) { + emit8(0x48); emit8(0x01); emit8(0xD0); + pad32(); + return; + } else if (p == PRIM_SUB) { + emit8(0x48); emit8(0x29); emit8(0xD0); + pad32(); + return; + } else if (p == PRIM_FETCH) { + emit8(0x48); emit8(0x8B); emit8(0x04); emit8(0xC3); // mov rax, [rbx + rax*8] + pad32(); + return; + } else if (p == PRIM_DEC) { + emit8(0x48); emit8(0xFF); emit8(0xC8); + pad32(); + return; + } else if (p == PRIM_STORE) { + emit8(0x48); emit8(0x89); emit8(0x14); emit8(0xC3); // mov [rbx + rax*8], rdx + pad32(); + return; + } else if (p == PRIM_RET_Z) { + emit8(0x48); emit8(0x85); emit8(0xC0); + emit8(0x75); emit8(0x01); + emit8(0xC3); + pad32(); + return; + } else if (p == PRIM_RET_S) { + emit8(0x48); emit8(0x85); emit8(0xC0); + emit8(0x79); emit8(0x01); + emit8(0xC3); + pad32(); + return; + } else if (p == PRIM_RET) { + emit8(0xC3); + pad32(); + return; + } else if (p == PRIM_DUP) { + emit8(0x48); emit8(0x89); emit8(0xC2); + pad32(); + return; + } else if (p == PRIM_DROP) { + emit8(0x48); emit8(0x89); emit8(0xD0); + pad32(); + return; + } else if (p == PRIM_PRINT) { + // FFI Dance: Save RDX, Align RSP (32 shadow + 8 align = 40) + emit8(0x52); // push rdx + emit8(0x48); emit8(0x83); emit8(0xEC); emit8(0x28); // sub rsp, 40 + + // Map arguments: RCX=RAX, RDX=RDX(already loaded), R8=Globals[0], R9=Globals[1] + emit8(0x48); emit8(0x89); emit8(0xC1); // mov rcx, rax + emit8(0x4C); emit8(0x8B); emit8(0x03); // mov r8, [rbx] + emit8(0x4C); emit8(0x8B); emit8(0x4B); emit8(0x08); // mov r9, [rbx+8] + + // Load func ptr and call + emit8(0x49); emit8(0xBA); // mov r10, ... + U8 addr = u8_(& ms_builtin_print); + emit32(u4_(addr & 0xFFFFFFFF)); + emit32(u4_(addr >> 32)); + emit8(0x41); emit8(0xFF); emit8(0xD2); // call r10 + + // Restore + emit8(0x48); emit8(0x83); emit8(0xC4); emit8(0x28); // add rsp, 40 + emit8(0x5A); // pop rdx + pad32(); + return; + } + } + + if (val > 0 && val < 0x10000) { + U4 target = tape_to_code_offset[val]; + pad32(); + S4 rel32 = s4_(target) - s4_(code_arena.used + 5); + emit8(0xE8); + emit32(u4_(rel32)); + pad32(); + } +} + +IA_ void compile_and_run_tape(void) +{ + farena_reset(& code_arena); + log_count = 0; + gdi_log_count = 0; + + emit8(0x53); // push rbx (callee-saved; also aligns RSP to 0 mod 16) + emit8(0x48); emit8(0x89); emit8(0xCB); // mov rbx, rcx (stable globals ptr for whole JIT session) + emit8(0x48); emit8(0x8B); emit8(0x43); emit8(0x70); // mov rax, [rbx+0x70] + emit8(0x48); emit8(0x8B); emit8(0x53); emit8(0x78); // mov rdx, [rbx+0x78] + + U4*r tape_ptr = u4_r(tape_arena.start); + U8*r anno_ptr = u8_r(anno_arena.start); + B4 in_def = false; + U4 def_jmp_offset = 0; + U8 end_idx = run_full ? (tape_arena.used / sizeof(U4)) : (cursor_idx + 1); + for (U8 i = 0; i < end_idx; i++) + { + U4 tag = unpack_tag(tape_ptr[i]); + U4 val = unpack_val(tape_ptr[i]); + + if (tag == STag_Define) + { + if (in_def == false) { + pad32(); + emit8(0xE9); + def_jmp_offset = code_arena.used; + emit32(0); + pad32(); + in_def = true; + } else { + emit8(0xC3); + pad32(); + } + + tape_to_code_offset[i] = code_arena.used; + + emit8(0x48); emit8(0x87); emit8(0xC2); + pad32(); + } + else if (tag == STag_Call || tag == STag_Imm) + { + char* name = (char*)&anno_ptr[i]; + char val_hex[9]; + u64_to_hex(val, val_hex, 8); + val_hex[8] = '\0'; + KTL_Slot_Str8 call_log_table[] = { + { ktl_str8_key("name"), str8(name) }, + { ktl_str8_key("val"), str8(val_hex) }, + }; + debug_log(str8("Compiling call: (val: )"), ktl_str8_from_arr(call_log_table)); + + if (tag == STag_Imm && in_def) { + emit8(0xC3); + pad32(); + U4 current = code_arena.used; + u4_r(code_arena.start + def_jmp_offset)[0] = current - (def_jmp_offset + 4); + in_def = false; + } + compile_action(val); + } + else if (tag == STag_Data) { + emit8(0x48); emit8(0x89); emit8(0xC2); + emit8(0x48); emit8(0xC7); emit8(0xC0); emit32(val); + pad32(); + } + } + + if (in_def) { + emit8(0xC3); + pad32(); + U4 current = code_arena.used; + u4_r(code_arena.start + def_jmp_offset)[0] = current - (def_jmp_offset + 4); + } + + emit8(0x48); emit8(0x89); emit8(0x43); emit8(0x70); // mov [rbx+0x70], rax + emit8(0x48); emit8(0x89); emit8(0x53); emit8(0x78); // mov [rbx+0x78], rdx + emit8(0x5B); // pop rbx + emit8(0xC3); // ret + + typedef void JIT_Func(U8* globals_ptr); + JIT_Func* func = (JIT_Func*)code_arena.start; + func(vm_globals); + + vm_rax = vm_globals[14]; + vm_rdx = vm_globals[15]; + + char rax_hex[9]; + u64_to_hex(vm_rax, rax_hex, 8); + rax_hex[8] = '\0'; + char rdx_hex[9]; + u64_to_hex(vm_rdx, rdx_hex, 8); + rdx_hex[8] = '\0'; + KTL_Slot_Str8 post_jit_log_table[] = { + { ktl_str8_key("rax"), str8(rax_hex) }, + { ktl_str8_key("rdx"), str8(rdx_hex) }, + }; + debug_log(str8("JIT finished. RAX: , RDX: "), ktl_str8_from_arr(post_jit_log_table)); +} + +S8 win_proc(void* hwnd, U4 msg, U8 wparam, S8 lparam) +{ + U8 tape_count = tape_arena.used / sizeof(U4); + U4*r tape_ptr = u4_r(tape_arena.start); + switch (msg) { + case MS_WM_CHAR: { + if (editor_mode != MODE_EDIT) return 0; + + U4 t = tape_ptr[cursor_idx]; + U4 tag = unpack_tag(t); + U4 val = unpack_val(t); + U1 c = u1_(wparam); + + B4 should_skip = c < 32 || (c == 'e' && mode_switch_now); + if (should_skip) { mode_switch_now = false; return 0; } + + if (tag == STag_Data) { + U4 digit = 16; + if (c >= '0' && c <= '9') digit = c - '0'; + if (c >= 'a' && c <= 'f') digit = c - 'a' + 10; + if (c >= 'A' && c <= 'F') digit = c - 'A' + 10; + if (digit < 16) { + val = ((val << 4) | digit) & 0x0FFFFFFF; + tape_ptr[cursor_idx] = pack_token(tag, val); + } + } + else if (tag != STag_Format) { + U8*r anno_ptr = u8_r(anno_arena.start); + char* anno_str = (char*) & anno_ptr[cursor_idx]; + int len = 0; + while (len < 8 && anno_str[len] != '\0' && anno_str[len] != ' ') len ++; + if (len < 8) { + anno_str[len] = (char)c; + for (int i = len + 1; i < 8; i++) anno_str[i] = '\0'; + + if (tag == STag_Call || tag == STag_Imm || tag == STag_Define) { + U4 new_val = resolve_name_to_index(anno_str); + tape_ptr[cursor_idx] = pack_token(tag, new_val); + if (tag == STag_Define) relink_tape(); + } + } + } + vm_rax = 0; vm_rdx = 0; mem_zero(u8_(vm_globals), sizeof(vm_globals)); + compile_and_run_tape(); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_KEYDOWN: { + + if (wparam == 0x45 && editor_mode == MODE_NAV) { + editor_mode = MODE_EDIT; + mode_switch_now = true; + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + if (wparam == 0x1B && editor_mode == MODE_EDIT) { + editor_mode = MODE_NAV; + relink_tape(); + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + if (editor_mode == MODE_EDIT) { + if (wparam == MS_VK_BACK) { + U4 t = tape_ptr[cursor_idx]; + U4 tag = unpack_tag(t); + U4 val = unpack_val(t); + if (tag == STag_Data) { + val = val >> 4; + tape_ptr[cursor_idx] = pack_token(tag, val); + } + else if (tag != STag_Format) { + U8*r anno_ptr = u8_r(anno_arena.start); + char* anno_str = (char*) & anno_ptr[cursor_idx]; + int len = 0; + while (len < 8 && anno_str[len] != '\0' && anno_str[len] != ' ') len ++; + if (len > 0) { + anno_str[len - 1] = '\0'; + if (tag == STag_Call || tag == STag_Imm || tag == STag_Define) { + U4 new_val = resolve_name_to_index(anno_str); + tape_ptr[cursor_idx] = pack_token(tag, new_val); + if (tag == STag_Define) relink_tape(); + } + } + } + vm_rax = 0; vm_rdx = 0; mem_zero(u8_(vm_globals), sizeof(vm_globals)); + compile_and_run_tape(); + ms_invalidate_rect(hwnd, nullptr, true); + } + return 0; + } + + if (wparam == MS_VK_RIGHT && cursor_idx < tape_count - 1) cursor_idx ++; + if (wparam == MS_VK_LEFT && cursor_idx > 0) cursor_idx --; + + if (wparam == MS_VK_UP) { + U8 line_start = cursor_idx; + while (line_start > 0 && unpack_tag(tape_ptr[line_start - 1]) != STag_Format) line_start--; + if (line_start > 0) { + U8 col = cursor_idx - line_start; + U8 prev_line_start = line_start - 1; + while (prev_line_start > 0 && unpack_tag(tape_ptr[prev_line_start - 1]) != STag_Format) prev_line_start--; + U8 prev_line_len = (line_start - 1) - prev_line_start; + cursor_idx = prev_line_start + (col < prev_line_len ? col : prev_line_len); + } + } + if (wparam == MS_VK_DOWN) { + U8 line_start = cursor_idx; + while (line_start > 0 && unpack_tag(tape_ptr[line_start - 1]) != STag_Format) line_start --; + U8 col = cursor_idx - line_start; + U8 next_line_start = cursor_idx; + while (next_line_start < tape_count && unpack_tag(tape_ptr[next_line_start]) != STag_Format) next_line_start ++; + if (next_line_start < tape_count) { + next_line_start ++; + U8 next_line_end = next_line_start; + while (next_line_end < tape_count && unpack_tag(tape_ptr[next_line_end]) != STag_Format) next_line_end ++; + U8 next_line_len = next_line_end - next_line_start; + cursor_idx = next_line_start + (col < next_line_len ? col : next_line_len); + } + } + + if (wparam == MS_VK_PRIOR) { scroll_y_offset -= 100; if (scroll_y_offset < 0) scroll_y_offset = 0; } + if (wparam == MS_VK_NEXT) { scroll_y_offset += 100; } + if (wparam == MS_VK_F5) { run_full = !run_full; } + if (wparam == MS_VK_F1) { save_cartridge(); } + if (wparam == MS_VK_F2) { load_cartridge(); ms_invalidate_rect(hwnd, nullptr, true); } + + if (wparam == MS_VK_TAB) { + U4 t = tape_ptr[cursor_idx]; + U4 tag = (unpack_tag(t) + 1) % STag_Count; + tape_ptr[cursor_idx] = pack_token(tag, unpack_val(t)); + } + else if (wparam == MS_VK_BACK) + { + U8 delete_idx = cursor_idx; + B4 is_shift = (ms_get_async_key_state(MS_VK_SHIFT) & 0x8000) != 0; + if (is_shift == false) { + if (cursor_idx > 0) { + delete_idx = cursor_idx - 1; + cursor_idx--; + } + else return 0; + } + if (tape_count > 0) { + U8*r anno_ptr = u8_r(anno_arena.start); + for (U8 i = delete_idx; i < tape_count - 1; i ++) { + tape_ptr[i] = tape_ptr[i + 1]; + anno_ptr[i] = anno_ptr[i + 1]; + } + tape_arena.used -= sizeof(U4); + anno_arena.used -= sizeof(U8); + } + relink_tape(); + } + else if (wparam == MS_VK_SPACE || wparam == MS_VK_RETURN) { + B4 is_shift = (ms_get_async_key_state(MS_VK_SHIFT) & 0x8000) != 0; + U8 insert_idx = cursor_idx; + if (is_shift) insert_idx ++; + + if (tape_arena.used + sizeof(U4) <= tape_arena.capacity && anno_arena.used + sizeof(U8) <= anno_arena.capacity) { + U8*r anno_ptr = u8_r(anno_arena.start); + for (U8 i = tape_count; i > insert_idx; i --) { + tape_ptr[i] = tape_ptr[i-1]; + anno_ptr[i] = anno_ptr[i-1]; + } + if (wparam == MS_VK_RETURN) { + tape_ptr[insert_idx] = pack_token(STag_Format, 0xA); + anno_ptr[insert_idx] = 0; + } else { + tape_ptr[insert_idx] = pack_token(STag_Comment, 0); + anno_ptr[insert_idx] = 0; + } + if (is_shift) cursor_idx ++; + tape_arena.used += sizeof(U4); + anno_arena.used += sizeof(U8); + } + } + + vm_rax = 0; vm_rdx = 0; + mem_zero(u8_(vm_globals), sizeof(vm_globals)); + + compile_and_run_tape(); + + ms_invalidate_rect(hwnd, nullptr, true); + return 0; + } + case MS_WM_PAINT: { + MS_PAINTSTRUCT ps; + void* hdc = ms_begin_paint(hwnd, & ps); + void* hFont = ms_create_font_a(20, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, "Consolas"); + void* hOldFont = ms_select_object(hdc, hFont); + + ms_set_bk_mode(hdc, 1); + + void* hBgBrush = ms_create_solid_brush(0x00222222); + ms_select_object(hdc, hBgBrush); + ms_rectangle(hdc, -1, -1, 3000, 3000); + + void* hBrushEdit = ms_create_solid_brush(0x008E563B); + void* hBrushNav = ms_create_solid_brush(0x00262F3B); + + S4 start_x = 40, start_y = 60, spacing_x = 110, spacing_y = 35; + S4 x = start_x, y = start_y; + + U4*r tape_ptr = u4_r(tape_arena.start); + U8*r anno_ptr = u8_r(anno_arena.start); + + for (U8 i = 0; i < tape_count; i++) + { + if (x >= start_x + (TOKENS_PER_ROW * spacing_x)) { + x = start_x; y += spacing_y; + } + S4 render_y = y - scroll_y_offset; + if (i == cursor_idx && render_y >= 30 && render_y < 500) { + ms_select_object(hdc, editor_mode == MODE_EDIT ? hBrushEdit : hBrushNav); + ms_rectangle(hdc, x - 5, render_y - 2, x + 95, render_y + 22); + } + if (render_y >= 30 && render_y < 500) + { + U4 t = tape_ptr[i]; + U4 tag = unpack_tag(t); + U4 val = unpack_val(t); + U8 anno = anno_ptr[i]; + if (tag == STag_Format && val == 0xA) { + ms_set_text_color(hdc, 0x00444444); + ms_text_out_a(hdc, x, render_y, " \\n ", 6); + x = start_x; + y += spacing_y; + } + else + { + U4 color = tag_colors[tag]; + const char* prefix = tag_prefixes[tag]; + + ms_set_text_color(hdc, color); + if (editor_mode == MODE_EDIT && i == cursor_idx) { + ms_set_text_color(hdc, 0x001E1E1E); + } + + char val_str[9]; + if (tag == STag_Data) { + u64_to_hex(val, val_str, 6); + val_str[6] = '\0'; + } + else + { + char* a_str = (char*) & anno; + for(int c=0; c<8; c++) { + val_str[c] = a_str[c] ? a_str[c] : ' '; + } + val_str[8] = '\0'; + } + char out_buf[12]; + out_buf[0] = prefix[0]; + out_buf[1] = ' '; + mem_copy(u8_(out_buf + 2), u8_(val_str), 8); + out_buf[10] = '\0'; + + ms_text_out_a(hdc, x, render_y, out_buf, 10); + x += spacing_x; + } + } + else if (unpack_tag(tape_ptr[i]) == STag_Format && unpack_val(tape_ptr[i]) == 0xA) { + x = start_x; + y += spacing_y; + } + else { + x += spacing_x; + } + } + + void* hHudBrush = ms_create_solid_brush(0x00141E23); + ms_select_object(hdc, hHudBrush); + ms_rectangle(hdc, -1, 500, 3000, 3000); + ms_rectangle(hdc, -1, -1, 3000, 40); + + ms_set_text_color(hdc, 0x00AAAAAA); + ms_text_out_a(hdc, 40, 10, "x86-64 Machine Code Emitter | 2-Reg Stack | [F5] Toggle Run Mode | [PgUp/PgDn] Scroll", 85); + + ms_set_text_color(hdc, 0x00FFFFFF); + char jit_str[64] = "Mode: Incremental | JIT Size: 0x000 bytes"; + if (run_full) mem_copy(u8_(jit_str + 6), u8_("Full "), 11); + u64_to_hex(code_arena.used, jit_str + 32, 3); + ms_text_out_a(hdc, 40, 520, jit_str, 41); + + char state_str[64] = "RAX: 00000000 | RDX: 00000000"; + u64_to_hex(vm_rax, state_str + 5, 8); + u64_to_hex(vm_rdx, state_str + 21, 8); + ms_set_text_color(hdc, 0x0094BAA1); + ms_text_out_a(hdc, 40, 550, state_str, 29); + + if (tape_count > 0 && cursor_idx < tape_count) { + U4 cur_tag = unpack_tag(tape_ptr[cursor_idx]); + const char* tag_name = tag_names [cur_tag]; + U4 cur_color = tag_colors[cur_tag]; + char semantics_str[64] = "Current Tag: "; + U4 name_len = 0; + while (tag_name[name_len]) { + semantics_str[13 + name_len] = tag_name[name_len]; + name_len ++; + } + semantics_str[13 + name_len] = '\0'; + ms_set_text_color(hdc, cur_color); + ms_text_out_a(hdc, 40, 580, semantics_str, 13 + name_len); + } + + ms_set_text_color(hdc, 0x00C8C8C8); + ms_text_out_a(hdc, 400, 520, "Global Memory (Contiguous Array):", 33); + for (int i=0; i < 4; i ++) { + char glob_str[32] = "[0]: 00000000"; + glob_str[1] = '0' + i; + u64_to_hex(vm_globals[i], glob_str + 5, 8); + ms_set_text_color(hdc, 0x00D6A454); + ms_text_out_a(hdc, 400, 550 + (i * 25), glob_str, 13); + } + + ms_set_text_color(hdc, 0x00C8C8C8); + ms_text_out_a(hdc, 750, 520, "Print Log:", 10); + for (int i = 0; i +#include +#include +#include "microui.h" + +#define unused(x) ((void) (x)) + +#define expect(x) do { \ + if (!(x)) { \ + fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \ + __FILE__, __LINE__, #x); \ + abort(); \ + } \ + } while (0) + +#define push(stk, val) do { \ + expect((stk).idx < (int) (sizeof((stk).items) / sizeof(*(stk).items))); \ + (stk).items[(stk).idx] = (val); \ + (stk).idx++; /* incremented after incase `val` uses this value */ \ + } while (0) + +#define pop(stk) do { \ + expect((stk).idx > 0); \ + (stk).idx--; \ + } while (0) + + +static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 }; + +static mu_Style default_style = { + /* font | size | padding | spacing | indent */ + NULL, { 68, 10 }, 5, 4, 24, + /* title_height | scrollbar_size | thumb_size */ + 24, 12, 8, + { + { 230, 230, 230, 255 }, /* MU_COLOR_TEXT */ + { 25, 25, 25, 255 }, /* MU_COLOR_BORDER */ + { 50, 50, 50, 255 }, /* MU_COLOR_WINDOWBG */ + { 25, 25, 25, 255 }, /* MU_COLOR_TITLEBG */ + { 240, 240, 240, 255 }, /* MU_COLOR_TITLETEXT */ + { 0, 0, 0, 0 }, /* MU_COLOR_PANELBG */ + { 75, 75, 75, 255 }, /* MU_COLOR_BUTTON */ + { 95, 95, 95, 255 }, /* MU_COLOR_BUTTONHOVER */ + { 115, 115, 115, 255 }, /* MU_COLOR_BUTTONFOCUS */ + { 30, 30, 30, 255 }, /* MU_COLOR_BASE */ + { 35, 35, 35, 255 }, /* MU_COLOR_BASEHOVER */ + { 40, 40, 40, 255 }, /* MU_COLOR_BASEFOCUS */ + { 43, 43, 43, 255 }, /* MU_COLOR_SCROLLBASE */ + { 30, 30, 30, 255 } /* MU_COLOR_SCROLLTHUMB */ + } +}; + + +mu_Vec2 mu_vec2(int x, int y) { + mu_Vec2 res; + res.x = x; res.y = y; + return res; +} + + +mu_Rect mu_rect(int x, int y, int w, int h) { + mu_Rect res; + res.x = x; res.y = y; res.w = w; res.h = h; + return res; +} + + +mu_Color mu_color(int r, int g, int b, int a) { + mu_Color res; + res.r = r; res.g = g; res.b = b; res.a = a; + return res; +} + + +static mu_Rect expand_rect(mu_Rect rect, int n) { + return mu_rect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2); +} + + +static mu_Rect intersect_rects(mu_Rect r1, mu_Rect r2) { + int x1 = mu_max(r1.x, r2.x); + int y1 = mu_max(r1.y, r2.y); + int x2 = mu_min(r1.x + r1.w, r2.x + r2.w); + int y2 = mu_min(r1.y + r1.h, r2.y + r2.h); + if (x2 < x1) { x2 = x1; } + if (y2 < y1) { y2 = y1; } + return mu_rect(x1, y1, x2 - x1, y2 - y1); +} + + +static int rect_overlaps_vec2(mu_Rect r, mu_Vec2 p) { + return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h; +} + + +static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid) { + mu_draw_rect(ctx, rect, ctx->style->colors[colorid]); + if (colorid == MU_COLOR_SCROLLBASE || + colorid == MU_COLOR_SCROLLTHUMB || + colorid == MU_COLOR_TITLEBG) { return; } + /* draw border */ + if (ctx->style->colors[MU_COLOR_BORDER].a) { + mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_BORDER]); + } +} + + +void mu_init(mu_Context *ctx) { + memset(ctx, 0, sizeof(*ctx)); + ctx->draw_frame = draw_frame; + ctx->_style = default_style; + ctx->style = &ctx->_style; +} + + +void mu_begin(mu_Context *ctx) { + expect(ctx->text_width && ctx->text_height); + ctx->command_list.idx = 0; + ctx->root_list.idx = 0; + ctx->scroll_target = NULL; + ctx->hover_root = ctx->next_hover_root; + ctx->next_hover_root = NULL; + ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x; + ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y; + ctx->frame++; +} + + +static int compare_zindex(const void *a, const void *b) { + return (*(mu_Container**) a)->zindex - (*(mu_Container**) b)->zindex; +} + + +void mu_end(mu_Context *ctx) { + int i, n; + /* check stacks */ + expect(ctx->container_stack.idx == 0); + expect(ctx->clip_stack.idx == 0); + expect(ctx->id_stack.idx == 0); + expect(ctx->layout_stack.idx == 0); + + /* handle scroll input */ + if (ctx->scroll_target) { + ctx->scroll_target->scroll.x += ctx->scroll_delta.x; + ctx->scroll_target->scroll.y += ctx->scroll_delta.y; + } + + /* unset focus if focus id was not touched this frame */ + if (!ctx->updated_focus) { ctx->focus = 0; } + ctx->updated_focus = 0; + + /* bring hover root to front if mouse was pressed */ + if (ctx->mouse_pressed && ctx->next_hover_root && + ctx->next_hover_root->zindex < ctx->last_zindex && + ctx->next_hover_root->zindex >= 0 + ) { + mu_bring_to_front(ctx, ctx->next_hover_root); + } + + /* reset input state */ + ctx->key_pressed = 0; + ctx->input_text[0] = '\0'; + ctx->mouse_pressed = 0; + ctx->scroll_delta = mu_vec2(0, 0); + ctx->last_mouse_pos = ctx->mouse_pos; + + /* sort root containers by zindex */ + n = ctx->root_list.idx; + qsort(ctx->root_list.items, n, sizeof(mu_Container*), compare_zindex); + + /* set root container jump commands */ + for (i = 0; i < n; i++) { + mu_Container *cnt = ctx->root_list.items[i]; + /* if this is the first container then make the first command jump to it. + ** otherwise set the previous container's tail to jump to this one */ + if (i == 0) { + mu_Command *cmd = (mu_Command*) ctx->command_list.items; + cmd->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand); + } else { + mu_Container *prev = ctx->root_list.items[i - 1]; + prev->tail->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand); + } + /* make the last container's tail jump to the end of command list */ + if (i == n - 1) { + cnt->tail->jump.dst = ctx->command_list.items + ctx->command_list.idx; + } + } +} + + +void mu_set_focus(mu_Context *ctx, mu_Id id) { + ctx->focus = id; + ctx->updated_focus = 1; +} + + +/* 32bit fnv-1a hash */ +#define HASH_INITIAL 2166136261 + +static void hash(mu_Id *hash, const void *data, int size) { + const unsigned char *p = data; + while (size--) { + *hash = (*hash ^ *p++) * 16777619; + } +} + + +mu_Id mu_get_id(mu_Context *ctx, const void *data, int size) { + int idx = ctx->id_stack.idx; + mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL; + hash(&res, data, size); + ctx->last_id = res; + return res; +} + + +void mu_push_id(mu_Context *ctx, const void *data, int size) { + push(ctx->id_stack, mu_get_id(ctx, data, size)); +} + + +void mu_pop_id(mu_Context *ctx) { + pop(ctx->id_stack); +} + + +void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect) { + mu_Rect last = mu_get_clip_rect(ctx); + push(ctx->clip_stack, intersect_rects(rect, last)); +} + + +void mu_pop_clip_rect(mu_Context *ctx) { + pop(ctx->clip_stack); +} + + +mu_Rect mu_get_clip_rect(mu_Context *ctx) { + expect(ctx->clip_stack.idx > 0); + return ctx->clip_stack.items[ctx->clip_stack.idx - 1]; +} + + +int mu_check_clip(mu_Context *ctx, mu_Rect r) { + mu_Rect cr = mu_get_clip_rect(ctx); + if (r.x > cr.x + cr.w || r.x + r.w < cr.x || + r.y > cr.y + cr.h || r.y + r.h < cr.y ) { return MU_CLIP_ALL; } + if (r.x >= cr.x && r.x + r.w <= cr.x + cr.w && + r.y >= cr.y && r.y + r.h <= cr.y + cr.h ) { return 0; } + return MU_CLIP_PART; +} + + +static void push_layout(mu_Context *ctx, mu_Rect body, mu_Vec2 scroll) { + mu_Layout layout; + int width = 0; + memset(&layout, 0, sizeof(layout)); + layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h); + layout.max = mu_vec2(-0x1000000, -0x1000000); + push(ctx->layout_stack, layout); + mu_layout_row(ctx, 1, &width, 0); +} + + +static mu_Layout* get_layout(mu_Context *ctx) { + return &ctx->layout_stack.items[ctx->layout_stack.idx - 1]; +} + + +static void pop_container(mu_Context *ctx) { + mu_Container *cnt = mu_get_current_container(ctx); + mu_Layout *layout = get_layout(ctx); + cnt->content_size.x = layout->max.x - layout->body.x; + cnt->content_size.y = layout->max.y - layout->body.y; + /* pop container, layout and id */ + pop(ctx->container_stack); + pop(ctx->layout_stack); + mu_pop_id(ctx); +} + + +mu_Container* mu_get_current_container(mu_Context *ctx) { + expect(ctx->container_stack.idx > 0); + return ctx->container_stack.items[ ctx->container_stack.idx - 1 ]; +} + + +static mu_Container* get_container(mu_Context *ctx, mu_Id id, int opt) { + mu_Container *cnt; + /* try to get existing container from pool */ + int idx = mu_pool_get(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id); + if (idx >= 0) { + if (ctx->containers[idx].open || ~opt & MU_OPT_CLOSED) { + mu_pool_update(ctx, ctx->container_pool, idx); + } + return &ctx->containers[idx]; + } + if (opt & MU_OPT_CLOSED) { return NULL; } + /* container not found in pool: init new container */ + idx = mu_pool_init(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id); + cnt = &ctx->containers[idx]; + memset(cnt, 0, sizeof(*cnt)); + cnt->open = 1; + mu_bring_to_front(ctx, cnt); + return cnt; +} + + +mu_Container* mu_get_container(mu_Context *ctx, const char *name) { + mu_Id id = mu_get_id(ctx, name, strlen(name)); + return get_container(ctx, id, 0); +} + + +void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt) { + cnt->zindex = ++ctx->last_zindex; +} + + +/*============================================================================ +** pool +**============================================================================*/ + +int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) { + int i, n = -1, f = ctx->frame; + for (i = 0; i < len; i++) { + if (items[i].last_update < f) { + f = items[i].last_update; + n = i; + } + } + expect(n > -1); + items[n].id = id; + mu_pool_update(ctx, items, n); + return n; +} + + +int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) { + int i; + unused(ctx); + for (i = 0; i < len; i++) { + if (items[i].id == id) { return i; } + } + return -1; +} + + +void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx) { + items[idx].last_update = ctx->frame; +} + + +/*============================================================================ +** input handlers +**============================================================================*/ + +void mu_input_mousemove(mu_Context *ctx, int x, int y) { + ctx->mouse_pos = mu_vec2(x, y); +} + + +void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn) { + mu_input_mousemove(ctx, x, y); + ctx->mouse_down |= btn; + ctx->mouse_pressed |= btn; +} + + +void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn) { + mu_input_mousemove(ctx, x, y); + ctx->mouse_down &= ~btn; +} + + +void mu_input_scroll(mu_Context *ctx, int x, int y) { + ctx->scroll_delta.x += x; + ctx->scroll_delta.y += y; +} + + +void mu_input_keydown(mu_Context *ctx, int key) { + ctx->key_pressed |= key; + ctx->key_down |= key; +} + + +void mu_input_keyup(mu_Context *ctx, int key) { + ctx->key_down &= ~key; +} + + +void mu_input_text(mu_Context *ctx, const char *text) { + int len = strlen(ctx->input_text); + int size = strlen(text) + 1; + expect(len + size <= (int) sizeof(ctx->input_text)); + memcpy(ctx->input_text + len, text, size); +} + + +/*============================================================================ +** commandlist +**============================================================================*/ + +mu_Command* mu_push_command(mu_Context *ctx, int type, int size) { + mu_Command *cmd = (mu_Command*) (ctx->command_list.items + ctx->command_list.idx); + expect(ctx->command_list.idx + size < MU_COMMANDLIST_SIZE); + cmd->base.type = type; + cmd->base.size = size; + ctx->command_list.idx += size; + return cmd; +} + + +int mu_next_command(mu_Context *ctx, mu_Command **cmd) { + if (*cmd) { + *cmd = (mu_Command*) (((char*) *cmd) + (*cmd)->base.size); + } else { + *cmd = (mu_Command*) ctx->command_list.items; + } + while ((char*) *cmd != ctx->command_list.items + ctx->command_list.idx) { + if ((*cmd)->type != MU_COMMAND_JUMP) { return 1; } + *cmd = (*cmd)->jump.dst; + } + return 0; +} + + +static mu_Command* push_jump(mu_Context *ctx, mu_Command *dst) { + mu_Command *cmd; + cmd = mu_push_command(ctx, MU_COMMAND_JUMP, sizeof(mu_JumpCommand)); + cmd->jump.dst = dst; + return cmd; +} + + +void mu_set_clip(mu_Context *ctx, mu_Rect rect) { + mu_Command *cmd; + cmd = mu_push_command(ctx, MU_COMMAND_CLIP, sizeof(mu_ClipCommand)); + cmd->clip.rect = rect; +} + + +void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color) { + mu_Command *cmd; + rect = intersect_rects(rect, mu_get_clip_rect(ctx)); + if (rect.w > 0 && rect.h > 0) { + cmd = mu_push_command(ctx, MU_COMMAND_RECT, sizeof(mu_RectCommand)); + cmd->rect.rect = rect; + cmd->rect.color = color; + } +} + + +void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color) { + mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color); + mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color); + mu_draw_rect(ctx, mu_rect(rect.x, rect.y, 1, rect.h), color); + mu_draw_rect(ctx, mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color); +} + + +void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len, + mu_Vec2 pos, mu_Color color) +{ + mu_Command *cmd; + mu_Rect rect = mu_rect( + pos.x, pos.y, ctx->text_width(font, str, len), ctx->text_height(font)); + int clipped = mu_check_clip(ctx, rect); + if (clipped == MU_CLIP_ALL ) { return; } + if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); } + /* add command */ + if (len < 0) { len = strlen(str); } + cmd = mu_push_command(ctx, MU_COMMAND_TEXT, sizeof(mu_TextCommand) + len); + memcpy(cmd->text.str, str, len); + cmd->text.str[len] = '\0'; + cmd->text.pos = pos; + cmd->text.color = color; + cmd->text.font = font; + /* reset clipping if it was set */ + if (clipped) { mu_set_clip(ctx, unclipped_rect); } +} + + +void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color) { + mu_Command *cmd; + /* do clip command if the rect isn't fully contained within the cliprect */ + int clipped = mu_check_clip(ctx, rect); + if (clipped == MU_CLIP_ALL ) { return; } + if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); } + /* do icon command */ + cmd = mu_push_command(ctx, MU_COMMAND_ICON, sizeof(mu_IconCommand)); + cmd->icon.id = id; + cmd->icon.rect = rect; + cmd->icon.color = color; + /* reset clipping if it was set */ + if (clipped) { mu_set_clip(ctx, unclipped_rect); } +} + + +/*============================================================================ +** layout +**============================================================================*/ + +enum { RELATIVE = 1, ABSOLUTE = 2 }; + + +void mu_layout_begin_column(mu_Context *ctx) { + push_layout(ctx, mu_layout_next(ctx), mu_vec2(0, 0)); +} + + +void mu_layout_end_column(mu_Context *ctx) { + mu_Layout *a, *b; + b = get_layout(ctx); + pop(ctx->layout_stack); + /* inherit position/next_row/max from child layout if they are greater */ + a = get_layout(ctx); + a->position.x = mu_max(a->position.x, b->position.x + b->body.x - a->body.x); + a->next_row = mu_max(a->next_row, b->next_row + b->body.y - a->body.y); + a->max.x = mu_max(a->max.x, b->max.x); + a->max.y = mu_max(a->max.y, b->max.y); +} + + +void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height) { + mu_Layout *layout = get_layout(ctx); + if (widths) { + expect(items <= MU_MAX_WIDTHS); + memcpy(layout->widths, widths, items * sizeof(widths[0])); + } + layout->items = items; + layout->position = mu_vec2(layout->indent, layout->next_row); + layout->size.y = height; + layout->item_index = 0; +} + + +void mu_layout_width(mu_Context *ctx, int width) { + get_layout(ctx)->size.x = width; +} + + +void mu_layout_height(mu_Context *ctx, int height) { + get_layout(ctx)->size.y = height; +} + + +void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative) { + mu_Layout *layout = get_layout(ctx); + layout->next = r; + layout->next_type = relative ? RELATIVE : ABSOLUTE; +} + + +mu_Rect mu_layout_next(mu_Context *ctx) { + mu_Layout *layout = get_layout(ctx); + mu_Style *style = ctx->style; + mu_Rect res; + + if (layout->next_type) { + /* handle rect set by `mu_layout_set_next` */ + int type = layout->next_type; + layout->next_type = 0; + res = layout->next; + if (type == ABSOLUTE) { return (ctx->last_rect = res); } + + } else { + /* handle next row */ + if (layout->item_index == layout->items) { + mu_layout_row(ctx, layout->items, NULL, layout->size.y); + } + + /* position */ + res.x = layout->position.x; + res.y = layout->position.y; + + /* size */ + res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x; + res.h = layout->size.y; + if (res.w == 0) { res.w = style->size.x + style->padding * 2; } + if (res.h == 0) { res.h = style->size.y + style->padding * 2; } + if (res.w < 0) { res.w += layout->body.w - res.x + 1; } + if (res.h < 0) { res.h += layout->body.h - res.y + 1; } + + layout->item_index++; + } + + /* update position */ + layout->position.x += res.w + style->spacing; + layout->next_row = mu_max(layout->next_row, res.y + res.h + style->spacing); + + /* apply body offset */ + res.x += layout->body.x; + res.y += layout->body.y; + + /* update max position */ + layout->max.x = mu_max(layout->max.x, res.x + res.w); + layout->max.y = mu_max(layout->max.y, res.y + res.h); + + return (ctx->last_rect = res); +} + + +/*============================================================================ +** controls +**============================================================================*/ + +static int in_hover_root(mu_Context *ctx) { + int i = ctx->container_stack.idx; + while (i--) { + if (ctx->container_stack.items[i] == ctx->hover_root) { return 1; } + /* only root containers have their `head` field set; stop searching if we've + ** reached the current root container */ + if (ctx->container_stack.items[i]->head) { break; } + } + return 0; +} + + +void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect, + int colorid, int opt) +{ + if (opt & MU_OPT_NOFRAME) { return; } + colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0; + ctx->draw_frame(ctx, rect, colorid); +} + + +void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, + int colorid, int opt) +{ + mu_Vec2 pos; + mu_Font font = ctx->style->font; + int tw = ctx->text_width(font, str, -1); + mu_push_clip_rect(ctx, rect); + pos.y = rect.y + (rect.h - ctx->text_height(font)) / 2; + if (opt & MU_OPT_ALIGNCENTER) { + pos.x = rect.x + (rect.w - tw) / 2; + } else if (opt & MU_OPT_ALIGNRIGHT) { + pos.x = rect.x + rect.w - tw - ctx->style->padding; + } else { + pos.x = rect.x + ctx->style->padding; + } + mu_draw_text(ctx, font, str, -1, pos, ctx->style->colors[colorid]); + mu_pop_clip_rect(ctx); +} + + +int mu_mouse_over(mu_Context *ctx, mu_Rect rect) { + return rect_overlaps_vec2(rect, ctx->mouse_pos) && + rect_overlaps_vec2(mu_get_clip_rect(ctx), ctx->mouse_pos) && + in_hover_root(ctx); +} + + +void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) { + int mouseover = mu_mouse_over(ctx, rect); + + if (ctx->focus == id) { ctx->updated_focus = 1; } + if (opt & MU_OPT_NOINTERACT) { return; } + if (mouseover && !ctx->mouse_down) { ctx->hover = id; } + + if (ctx->focus == id) { + if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); } + if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); } + } + + if (ctx->hover == id) { + if (ctx->mouse_pressed) { + mu_set_focus(ctx, id); + } else if (!mouseover) { + ctx->hover = 0; + } + } +} + + +void mu_text(mu_Context *ctx, const char *text) { + const char *start, *end, *p = text; + int width = -1; + mu_Font font = ctx->style->font; + mu_Color color = ctx->style->colors[MU_COLOR_TEXT]; + mu_layout_begin_column(ctx); + mu_layout_row(ctx, 1, &width, ctx->text_height(font)); + do { + mu_Rect r = mu_layout_next(ctx); + int w = 0; + start = end = p; + do { + const char* word = p; + while (*p && *p != ' ' && *p != '\n') { p++; } + w += ctx->text_width(font, word, p - word); + if (w > r.w && end != start) { break; } + w += ctx->text_width(font, p, 1); + end = p++; + } while (*end && *end != '\n'); + mu_draw_text(ctx, font, start, end - start, mu_vec2(r.x, r.y), color); + p = end + 1; + } while (*end); + mu_layout_end_column(ctx); +} + + +void mu_label(mu_Context *ctx, const char *text) { + mu_draw_control_text(ctx, text, mu_layout_next(ctx), MU_COLOR_TEXT, 0); +} + + +int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) { + int res = 0; + mu_Id id = label ? mu_get_id(ctx, label, strlen(label)) + : mu_get_id(ctx, &icon, sizeof(icon)); + mu_Rect r = mu_layout_next(ctx); + mu_update_control(ctx, id, r, opt); + /* handle click */ + if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { + res |= MU_RES_SUBMIT; + } + /* draw */ + mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, opt); + if (label) { mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, opt); } + if (icon) { mu_draw_icon(ctx, icon, r, ctx->style->colors[MU_COLOR_TEXT]); } + return res; +} + + +int mu_checkbox(mu_Context *ctx, const char *label, int *state) { + int res = 0; + mu_Id id = mu_get_id(ctx, &state, sizeof(state)); + mu_Rect r = mu_layout_next(ctx); + mu_Rect box = mu_rect(r.x, r.y, r.h, r.h); + mu_update_control(ctx, id, r, 0); + /* handle click */ + if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { + res |= MU_RES_CHANGE; + *state = !*state; + } + /* draw */ + mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0); + if (*state) { + mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]); + } + r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h); + mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); + return res; +} + + +int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, + int opt) +{ + int res = 0; + mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS); + + if (ctx->focus == id) { + /* handle text input */ + int len = strlen(buf); + int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text)); + if (n > 0) { + memcpy(buf + len, ctx->input_text, n); + len += n; + buf[len] = '\0'; + res |= MU_RES_CHANGE; + } + /* handle backspace */ + if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) { + /* skip utf-8 continuation bytes */ + while ((buf[--len] & 0xc0) == 0x80 && len > 0); + buf[len] = '\0'; + res |= MU_RES_CHANGE; + } + /* handle return */ + if (ctx->key_pressed & MU_KEY_RETURN) { + mu_set_focus(ctx, 0); + res |= MU_RES_SUBMIT; + } + } + + /* draw */ + mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt); + if (ctx->focus == id) { + mu_Color color = ctx->style->colors[MU_COLOR_TEXT]; + mu_Font font = ctx->style->font; + int textw = ctx->text_width(font, buf, -1); + int texth = ctx->text_height(font); + int ofx = r.w - ctx->style->padding - textw - 1; + int textx = r.x + mu_min(ofx, ctx->style->padding); + int texty = r.y + (r.h - texth) / 2; + mu_push_clip_rect(ctx, r); + mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color); + mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color); + mu_pop_clip_rect(ctx); + } else { + mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt); + } + + return res; +} + + +static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id) { + if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->key_down & MU_KEY_SHIFT && + ctx->hover == id + ) { + ctx->number_edit = id; + sprintf(ctx->number_edit_buf, MU_REAL_FMT, *value); + } + if (ctx->number_edit == id) { + int res = mu_textbox_raw( + ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0); + if (res & MU_RES_SUBMIT || ctx->focus != id) { + *value = strtod(ctx->number_edit_buf, NULL); + ctx->number_edit = 0; + } else { + return 1; + } + } + return 0; +} + + +int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) { + mu_Id id = mu_get_id(ctx, &buf, sizeof(buf)); + mu_Rect r = mu_layout_next(ctx); + return mu_textbox_raw(ctx, buf, bufsz, id, r, opt); +} + + +int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, + mu_Real step, const char *fmt, int opt) +{ + char buf[MU_MAX_FMT + 1]; + mu_Rect thumb; + int x, w, res = 0; + mu_Real last = *value, v = last; + mu_Id id = mu_get_id(ctx, &value, sizeof(value)); + mu_Rect base = mu_layout_next(ctx); + + /* handle text input mode */ + if (number_textbox(ctx, &v, base, id)) { return res; } + + /* handle normal mode */ + mu_update_control(ctx, id, base, opt); + + /* handle input */ + if (ctx->focus == id && + (ctx->mouse_down | ctx->mouse_pressed) == MU_MOUSE_LEFT) + { + v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w; + if (step) { v = ((long long)((v + step / 2) / step)) * step; } + } + /* clamp and store value, update res */ + *value = v = mu_clamp(v, low, high); + if (last != v) { res |= MU_RES_CHANGE; } + + /* draw base */ + mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt); + /* draw thumb */ + w = ctx->style->thumb_size; + x = (v - low) * (base.w - w) / (high - low); + thumb = mu_rect(base.x + x, base.y, w, base.h); + mu_draw_control_frame(ctx, id, thumb, MU_COLOR_BUTTON, opt); + /* draw text */ + sprintf(buf, fmt, v); + mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt); + + return res; +} + + +int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, + const char *fmt, int opt) +{ + char buf[MU_MAX_FMT + 1]; + int res = 0; + mu_Id id = mu_get_id(ctx, &value, sizeof(value)); + mu_Rect base = mu_layout_next(ctx); + mu_Real last = *value; + + /* handle text input mode */ + if (number_textbox(ctx, value, base, id)) { return res; } + + /* handle normal mode */ + mu_update_control(ctx, id, base, opt); + + /* handle input */ + if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { + *value += ctx->mouse_delta.x * step; + } + /* set flag if value changed */ + if (*value != last) { res |= MU_RES_CHANGE; } + + /* draw base */ + mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt); + /* draw text */ + sprintf(buf, fmt, *value); + mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt); + + return res; +} + + +static int header(mu_Context *ctx, const char *label, int istreenode, int opt) { + mu_Rect r; + int active, expanded; + mu_Id id = mu_get_id(ctx, label, strlen(label)); + int idx = mu_pool_get(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id); + int width = -1; + mu_layout_row(ctx, 1, &width, 0); + + active = (idx >= 0); + expanded = (opt & MU_OPT_EXPANDED) ? !active : active; + r = mu_layout_next(ctx); + mu_update_control(ctx, id, r, 0); + + /* handle click */ + active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id); + + /* update pool ref */ + if (idx >= 0) { + if (active) { mu_pool_update(ctx, ctx->treenode_pool, idx); } + else { memset(&ctx->treenode_pool[idx], 0, sizeof(mu_PoolItem)); } + } else if (active) { + mu_pool_init(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id); + } + + /* draw */ + if (istreenode) { + if (ctx->hover == id) { ctx->draw_frame(ctx, r, MU_COLOR_BUTTONHOVER); } + } else { + mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, 0); + } + mu_draw_icon( + ctx, expanded ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED, + mu_rect(r.x, r.y, r.h, r.h), ctx->style->colors[MU_COLOR_TEXT]); + r.x += r.h - ctx->style->padding; + r.w -= r.h - ctx->style->padding; + mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); + + return expanded ? MU_RES_ACTIVE : 0; +} + + +int mu_header_ex(mu_Context *ctx, const char *label, int opt) { + return header(ctx, label, 0, opt); +} + + +int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt) { + int res = header(ctx, label, 1, opt); + if (res & MU_RES_ACTIVE) { + get_layout(ctx)->indent += ctx->style->indent; + push(ctx->id_stack, ctx->last_id); + } + return res; +} + + +void mu_end_treenode(mu_Context *ctx) { + get_layout(ctx)->indent -= ctx->style->indent; + mu_pop_id(ctx); +} + + +#define scrollbar(ctx, cnt, b, cs, x, y, w, h) \ + do { \ + /* only add scrollbar if content size is larger than body */ \ + int maxscroll = cs.y - b->h; \ + \ + if (maxscroll > 0 && b->h > 0) { \ + mu_Rect base, thumb; \ + mu_Id id = mu_get_id(ctx, "!scrollbar" #y, 11); \ + \ + /* get sizing / positioning */ \ + base = *b; \ + base.x = b->x + b->w; \ + base.w = ctx->style->scrollbar_size; \ + \ + /* handle input */ \ + mu_update_control(ctx, id, base, 0); \ + if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { \ + cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h; \ + } \ + /* clamp scroll to limits */ \ + cnt->scroll.y = mu_clamp(cnt->scroll.y, 0, maxscroll); \ + \ + /* draw base and thumb */ \ + ctx->draw_frame(ctx, base, MU_COLOR_SCROLLBASE); \ + thumb = base; \ + thumb.h = mu_max(ctx->style->thumb_size, base.h * b->h / cs.y); \ + thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll; \ + ctx->draw_frame(ctx, thumb, MU_COLOR_SCROLLTHUMB); \ + \ + /* set this as the scroll_target (will get scrolled on mousewheel) */ \ + /* if the mouse is over it */ \ + if (mu_mouse_over(ctx, *b)) { ctx->scroll_target = cnt; } \ + } else { \ + cnt->scroll.y = 0; \ + } \ + } while (0) + + +static void scrollbars(mu_Context *ctx, mu_Container *cnt, mu_Rect *body) { + int sz = ctx->style->scrollbar_size; + mu_Vec2 cs = cnt->content_size; + cs.x += ctx->style->padding * 2; + cs.y += ctx->style->padding * 2; + mu_push_clip_rect(ctx, *body); + /* resize body to make room for scrollbars */ + if (cs.y > cnt->body.h) { body->w -= sz; } + if (cs.x > cnt->body.w) { body->h -= sz; } + /* to create a horizontal or vertical scrollbar almost-identical code is + ** used; only the references to `x|y` `w|h` need to be switched */ + scrollbar(ctx, cnt, body, cs, x, y, w, h); + scrollbar(ctx, cnt, body, cs, y, x, h, w); + mu_pop_clip_rect(ctx); +} + + +static void push_container_body( + mu_Context *ctx, mu_Container *cnt, mu_Rect body, int opt +) { + if (~opt & MU_OPT_NOSCROLL) { scrollbars(ctx, cnt, &body); } + push_layout(ctx, expand_rect(body, -ctx->style->padding), cnt->scroll); + cnt->body = body; +} + + +static void begin_root_container(mu_Context *ctx, mu_Container *cnt) { + push(ctx->container_stack, cnt); + /* push container to roots list and push head command */ + push(ctx->root_list, cnt); + cnt->head = push_jump(ctx, NULL); + /* set as hover root if the mouse is overlapping this container and it has a + ** higher zindex than the current hover root */ + if (rect_overlaps_vec2(cnt->rect, ctx->mouse_pos) && + (!ctx->next_hover_root || cnt->zindex > ctx->next_hover_root->zindex) + ) { + ctx->next_hover_root = cnt; + } + /* clipping is reset here in case a root-container is made within + ** another root-containers's begin/end block; this prevents the inner + ** root-container being clipped to the outer */ + push(ctx->clip_stack, unclipped_rect); +} + + +static void end_root_container(mu_Context *ctx) { + /* push tail 'goto' jump command and set head 'skip' command. the final steps + ** on initing these are done in mu_end() */ + mu_Container *cnt = mu_get_current_container(ctx); + cnt->tail = push_jump(ctx, NULL); + cnt->head->jump.dst = ctx->command_list.items + ctx->command_list.idx; + /* pop base clip rect and container */ + mu_pop_clip_rect(ctx); + pop_container(ctx); +} + + +int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt) { + mu_Rect body; + mu_Id id = mu_get_id(ctx, title, strlen(title)); + mu_Container *cnt = get_container(ctx, id, opt); + if (!cnt || !cnt->open) { return 0; } + push(ctx->id_stack, id); + + if (cnt->rect.w == 0) { cnt->rect = rect; } + begin_root_container(ctx, cnt); + rect = body = cnt->rect; + + /* draw frame */ + if (~opt & MU_OPT_NOFRAME) { + ctx->draw_frame(ctx, rect, MU_COLOR_WINDOWBG); + } + + /* do title bar */ + if (~opt & MU_OPT_NOTITLE) { + mu_Rect tr = rect; + tr.h = ctx->style->title_height; + ctx->draw_frame(ctx, tr, MU_COLOR_TITLEBG); + + /* do title text */ + if (~opt & MU_OPT_NOTITLE) { + mu_Id id = mu_get_id(ctx, "!title", 6); + mu_update_control(ctx, id, tr, opt); + mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt); + if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { + cnt->rect.x += ctx->mouse_delta.x; + cnt->rect.y += ctx->mouse_delta.y; + } + body.y += tr.h; + body.h -= tr.h; + } + + /* do `close` button */ + if (~opt & MU_OPT_NOCLOSE) { + mu_Id id = mu_get_id(ctx, "!close", 6); + mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h); + tr.w -= r.w; + mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]); + mu_update_control(ctx, id, r, opt); + if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) { + cnt->open = 0; + } + } + } + + push_container_body(ctx, cnt, body, opt); + + /* do `resize` handle */ + if (~opt & MU_OPT_NORESIZE) { + int sz = ctx->style->title_height; + mu_Id id = mu_get_id(ctx, "!resize", 7); + mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz); + mu_update_control(ctx, id, r, opt); + if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { + cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x); + cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y); + } + } + + /* resize to content size */ + if (opt & MU_OPT_AUTOSIZE) { + mu_Rect r = get_layout(ctx)->body; + cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w); + cnt->rect.h = cnt->content_size.y + (cnt->rect.h - r.h); + } + + /* close if this is a popup window and elsewhere was clicked */ + if (opt & MU_OPT_POPUP && ctx->mouse_pressed && ctx->hover_root != cnt) { + cnt->open = 0; + } + + mu_push_clip_rect(ctx, cnt->body); + return MU_RES_ACTIVE; +} + + +void mu_end_window(mu_Context *ctx) { + mu_pop_clip_rect(ctx); + end_root_container(ctx); +} + + +void mu_open_popup(mu_Context *ctx, const char *name) { + mu_Container *cnt = mu_get_container(ctx, name); + /* set as hover root so popup isn't closed in begin_window_ex() */ + ctx->hover_root = ctx->next_hover_root = cnt; + /* position at mouse cursor, open and bring-to-front */ + cnt->rect = mu_rect(ctx->mouse_pos.x, ctx->mouse_pos.y, 1, 1); + cnt->open = 1; + mu_bring_to_front(ctx, cnt); +} + + +int mu_begin_popup(mu_Context *ctx, const char *name) { + int opt = MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE | + MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED; + return mu_begin_window_ex(ctx, name, mu_rect(0, 0, 0, 0), opt); +} + + +void mu_end_popup(mu_Context *ctx) { + mu_end_window(ctx); +} + + +void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt) { + mu_Container *cnt; + mu_push_id(ctx, name, strlen(name)); + cnt = get_container(ctx, ctx->last_id, opt); + cnt->rect = mu_layout_next(ctx); + if (~opt & MU_OPT_NOFRAME) { + ctx->draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG); + } + push(ctx->container_stack, cnt); + push_container_body(ctx, cnt, cnt->rect, opt); + mu_push_clip_rect(ctx, cnt->body); +} + + +void mu_end_panel(mu_Context *ctx) { + mu_pop_clip_rect(ctx); + pop_container(ctx); +} diff --git a/attempt_1/microui.h b/attempt_1/microui.h new file mode 100644 index 0000000..6e18639 --- /dev/null +++ b/attempt_1/microui.h @@ -0,0 +1,296 @@ +/* +** Copyright (c) 2024 rxi +** +** This library is free software; you can redistribute it and/or modify it +** under the terms of the MIT license. See `microui.c` for details. +*/ + +#ifndef MICROUI_H +#define MICROUI_H + +#define MU_VERSION "2.02" + +#define MU_COMMANDLIST_SIZE (256 * 1024) +#define MU_ROOTLIST_SIZE 32 +#define MU_CONTAINERSTACK_SIZE 32 +#define MU_CLIPSTACK_SIZE 32 +#define MU_IDSTACK_SIZE 32 +#define MU_LAYOUTSTACK_SIZE 16 +#define MU_CONTAINERPOOL_SIZE 48 +#define MU_TREENODEPOOL_SIZE 48 +#define MU_MAX_WIDTHS 16 +#define MU_REAL float +#define MU_REAL_FMT "%.3g" +#define MU_SLIDER_FMT "%.2f" +#define MU_MAX_FMT 127 + +#define mu_stack(T, n) struct { int idx; T items[n]; } +#define mu_min(a, b) ((a) < (b) ? (a) : (b)) +#define mu_max(a, b) ((a) > (b) ? (a) : (b)) +#define mu_clamp(x, a, b) mu_min(b, mu_max(a, x)) + +enum { + MU_CLIP_PART = 1, + MU_CLIP_ALL +}; + +enum { + MU_COMMAND_JUMP = 1, + MU_COMMAND_CLIP, + MU_COMMAND_RECT, + MU_COMMAND_TEXT, + MU_COMMAND_ICON, + MU_COMMAND_MAX +}; + +enum { + MU_COLOR_TEXT, + MU_COLOR_BORDER, + MU_COLOR_WINDOWBG, + MU_COLOR_TITLEBG, + MU_COLOR_TITLETEXT, + MU_COLOR_PANELBG, + MU_COLOR_BUTTON, + MU_COLOR_BUTTONHOVER, + MU_COLOR_BUTTONFOCUS, + MU_COLOR_BASE, + MU_COLOR_BASEHOVER, + MU_COLOR_BASEFOCUS, + MU_COLOR_SCROLLBASE, + MU_COLOR_SCROLLTHUMB, + MU_COLOR_MAX +}; + +enum { + MU_ICON_CLOSE = 1, + MU_ICON_CHECK, + MU_ICON_COLLAPSED, + MU_ICON_EXPANDED, + MU_ICON_MAX +}; + +enum { + MU_RES_ACTIVE = (1 << 0), + MU_RES_SUBMIT = (1 << 1), + MU_RES_CHANGE = (1 << 2) +}; + +enum { + MU_OPT_ALIGNCENTER = (1 << 0), + MU_OPT_ALIGNRIGHT = (1 << 1), + MU_OPT_NOINTERACT = (1 << 2), + MU_OPT_NOFRAME = (1 << 3), + MU_OPT_NORESIZE = (1 << 4), + MU_OPT_NOSCROLL = (1 << 5), + MU_OPT_NOCLOSE = (1 << 6), + MU_OPT_NOTITLE = (1 << 7), + MU_OPT_HOLDFOCUS = (1 << 8), + MU_OPT_AUTOSIZE = (1 << 9), + MU_OPT_POPUP = (1 << 10), + MU_OPT_CLOSED = (1 << 11), + MU_OPT_EXPANDED = (1 << 12) +}; + +enum { + MU_MOUSE_LEFT = (1 << 0), + MU_MOUSE_RIGHT = (1 << 1), + MU_MOUSE_MIDDLE = (1 << 2) +}; + +enum { + MU_KEY_SHIFT = (1 << 0), + MU_KEY_CTRL = (1 << 1), + MU_KEY_ALT = (1 << 2), + MU_KEY_BACKSPACE = (1 << 3), + MU_KEY_RETURN = (1 << 4) +}; + + +typedef struct mu_Context mu_Context; +typedef unsigned mu_Id; +typedef MU_REAL mu_Real; +typedef void* mu_Font; + +typedef struct { int x, y; } mu_Vec2; +typedef struct { int x, y, w, h; } mu_Rect; +typedef struct { unsigned char r, g, b, a; } mu_Color; +typedef struct { mu_Id id; int last_update; } mu_PoolItem; + +typedef struct { int type, size; } mu_BaseCommand; +typedef struct { mu_BaseCommand base; void *dst; } mu_JumpCommand; +typedef struct { mu_BaseCommand base; mu_Rect rect; } mu_ClipCommand; +typedef struct { mu_BaseCommand base; mu_Rect rect; mu_Color color; } mu_RectCommand; +typedef struct { mu_BaseCommand base; mu_Font font; mu_Vec2 pos; mu_Color color; char str[1]; } mu_TextCommand; +typedef struct { mu_BaseCommand base; mu_Rect rect; int id; mu_Color color; } mu_IconCommand; + +typedef union { + int type; + mu_BaseCommand base; + mu_JumpCommand jump; + mu_ClipCommand clip; + mu_RectCommand rect; + mu_TextCommand text; + mu_IconCommand icon; +} mu_Command; + +typedef struct { + mu_Rect body; + mu_Rect next; + mu_Vec2 position; + mu_Vec2 size; + mu_Vec2 max; + int widths[MU_MAX_WIDTHS]; + int items; + int item_index; + int next_row; + int next_type; + int indent; +} mu_Layout; + +typedef struct { + mu_Command *head, *tail; + mu_Rect rect; + mu_Rect body; + mu_Vec2 content_size; + mu_Vec2 scroll; + int zindex; + int open; +} mu_Container; + +typedef struct { + mu_Font font; + mu_Vec2 size; + int padding; + int spacing; + int indent; + int title_height; + int scrollbar_size; + int thumb_size; + mu_Color colors[MU_COLOR_MAX]; +} mu_Style; + +struct mu_Context { + /* callbacks */ + int (*text_width)(mu_Font font, const char *str, int len); + int (*text_height)(mu_Font font); + void (*draw_frame)(mu_Context *ctx, mu_Rect rect, int colorid); + /* core state */ + mu_Style _style; + mu_Style *style; + mu_Id hover; + mu_Id focus; + mu_Id last_id; + mu_Rect last_rect; + int last_zindex; + int updated_focus; + int frame; + mu_Container *hover_root; + mu_Container *next_hover_root; + mu_Container *scroll_target; + char number_edit_buf[MU_MAX_FMT]; + mu_Id number_edit; + /* stacks */ + mu_stack(char, MU_COMMANDLIST_SIZE) command_list; + mu_stack(mu_Container*, MU_ROOTLIST_SIZE) root_list; + mu_stack(mu_Container*, MU_CONTAINERSTACK_SIZE) container_stack; + mu_stack(mu_Rect, MU_CLIPSTACK_SIZE) clip_stack; + mu_stack(mu_Id, MU_IDSTACK_SIZE) id_stack; + mu_stack(mu_Layout, MU_LAYOUTSTACK_SIZE) layout_stack; + /* retained state pools */ + mu_PoolItem container_pool[MU_CONTAINERPOOL_SIZE]; + mu_Container containers[MU_CONTAINERPOOL_SIZE]; + mu_PoolItem treenode_pool[MU_TREENODEPOOL_SIZE]; + /* input state */ + mu_Vec2 mouse_pos; + mu_Vec2 last_mouse_pos; + mu_Vec2 mouse_delta; + mu_Vec2 scroll_delta; + int mouse_down; + int mouse_pressed; + int key_down; + int key_pressed; + char input_text[32]; +}; + + +mu_Vec2 mu_vec2(int x, int y); +mu_Rect mu_rect(int x, int y, int w, int h); +mu_Color mu_color(int r, int g, int b, int a); + +void mu_init(mu_Context *ctx); +void mu_begin(mu_Context *ctx); +void mu_end(mu_Context *ctx); +void mu_set_focus(mu_Context *ctx, mu_Id id); +mu_Id mu_get_id(mu_Context *ctx, const void *data, int size); +void mu_push_id(mu_Context *ctx, const void *data, int size); +void mu_pop_id(mu_Context *ctx); +void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect); +void mu_pop_clip_rect(mu_Context *ctx); +mu_Rect mu_get_clip_rect(mu_Context *ctx); +int mu_check_clip(mu_Context *ctx, mu_Rect r); +mu_Container* mu_get_current_container(mu_Context *ctx); +mu_Container* mu_get_container(mu_Context *ctx, const char *name); +void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt); + +int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id); +int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id); +void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx); + +void mu_input_mousemove(mu_Context *ctx, int x, int y); +void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn); +void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn); +void mu_input_scroll(mu_Context *ctx, int x, int y); +void mu_input_keydown(mu_Context *ctx, int key); +void mu_input_keyup(mu_Context *ctx, int key); +void mu_input_text(mu_Context *ctx, const char *text); + +mu_Command* mu_push_command(mu_Context *ctx, int type, int size); +int mu_next_command(mu_Context *ctx, mu_Command **cmd); +void mu_set_clip(mu_Context *ctx, mu_Rect rect); +void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color); +void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color); +void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len, mu_Vec2 pos, mu_Color color); +void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color); + +void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height); +void mu_layout_width(mu_Context *ctx, int width); +void mu_layout_height(mu_Context *ctx, int height); +void mu_layout_begin_column(mu_Context *ctx); +void mu_layout_end_column(mu_Context *ctx); +void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative); +mu_Rect mu_layout_next(mu_Context *ctx); + +void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect, int colorid, int opt); +void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, int colorid, int opt); +int mu_mouse_over(mu_Context *ctx, mu_Rect rect); +void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt); + +#define mu_button(ctx, label) mu_button_ex(ctx, label, 0, MU_OPT_ALIGNCENTER) +#define mu_textbox(ctx, buf, bufsz) mu_textbox_ex(ctx, buf, bufsz, 0) +#define mu_slider(ctx, value, lo, hi) mu_slider_ex(ctx, value, lo, hi, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER) +#define mu_number(ctx, value, step) mu_number_ex(ctx, value, step, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER) +#define mu_header(ctx, label) mu_header_ex(ctx, label, 0) +#define mu_begin_treenode(ctx, label) mu_begin_treenode_ex(ctx, label, 0) +#define mu_begin_window(ctx, title, rect) mu_begin_window_ex(ctx, title, rect, 0) +#define mu_begin_panel(ctx, name) mu_begin_panel_ex(ctx, name, 0) + +void mu_text(mu_Context *ctx, const char *text); +void mu_label(mu_Context *ctx, const char *text); +int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt); +int mu_checkbox(mu_Context *ctx, const char *label, int *state); +int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, int opt); +int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt); +int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, mu_Real step, const char *fmt, int opt); +int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, const char *fmt, int opt); +int mu_header_ex(mu_Context *ctx, const char *label, int opt); +int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt); +void mu_end_treenode(mu_Context *ctx); +int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt); +void mu_end_window(mu_Context *ctx); +void mu_open_popup(mu_Context *ctx, const char *name); +int mu_begin_popup(mu_Context *ctx, const char *name); +void mu_end_popup(mu_Context *ctx); +void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt); +void mu_end_panel(mu_Context *ctx); + +#endif diff --git a/win_proc.c b/win_proc.c new file mode 100644 index 0000000..6a2ed9d Binary files /dev/null and b/win_proc.c differ