ref: 233b15c62e1c272d7017811b0287d590a34aba97
dir: /microui/microui.c/
#include <u.h> #include <libc.h> #include <draw.h> #include <microui.h> #define MU_REAL_FMT "%g" #define MU_SLIDER_FMT "%.2f" #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define CLAMP(x, a, b) MIN(b, MAX(a, x)) static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 }; static u8int atlasraw[] = { 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x72, 0x38, 0x67, 0x38, 0x62, 0x38, 0x61, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x30, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x33, 0x34, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x32, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x32, 0x35, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x34, 0x33, 0x39, 0x20, 0x80, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x78, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x34, 0x00, 0x80, 0x14, 0x00, 0x00, 0x80, 0x5c, 0x00, 0x00, 0x54, 0x1f, 0x04, 0x1b, 0x04, 0x23, 0x24, 0x13, 0x7c, 0x00, 0x3c, 0x00, 0x80, 0x20, 0x00, 0x00, 0x80, 0xc0, 0x00, 0x00, 0x04, 0x07, 0x44, 0x1f, 0x80, 0x5e, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x34, 0x1b, 0x04, 0x13, 0x04, 0x1b, 0x04, 0x23, 0x24, 0x17, 0x7c, 0x00, 0x2c, 0x00, 0x80, 0x21, 0x00, 0x00, 0x80, 0xe0, 0x00, 0x00, 0x80, 0xea, 0x00, 0x00, 0x80, 0x2c, 0x00, 0x00, 0x54, 0x27, 0x14, 0x6f, 0x24, 0x8b, 0x44, 0x13, 0x14, 0x00, 0x7c, 0x00, 0x4c, 0x83, 0x80, 0x2d, 0x00, 0x00, 0x74, 0x2f, 0x24, 0x77, 0x74, 0x83, 0x7c, 0x00, 0x1c, 0x83, 0x80, 0xe1, 0x00, 0x00, 0x80, 0xeb, 0x00, 0x00, 0x7c, 0x83, 0x3c, 0x8b, 0x74, 0x83, 0x24, 0x00, 0x80, 0x0e, 0x00, 0x00, 0x7c, 0x83, 0x7c, 0x83, 0x7c, 0x87, 0x3c, 0x00, 0x14, 0x00, 0x80, 0x3d, 0x00, 0x00, 0x80, 0xed, 0x00, 0x00, 0x80, 0x45, 0x00, 0x00, 0x24, 0x17, 0x80, 0x22, 0x00, 0x00, 0x7c, 0x83, 0x7d, 0x97, 0x75, 0x97, 0x14, 0x00, 0x80, 0x13, 0x00, 0x00, 0x80, 0xd0, 0x00, 0x00, 0x80, 0xf6, 0x00, 0x00, 0x14, 0x8b, 0x24, 0x83, 0x80, 0x2e, 0x00, 0x00, 0x7c, 0x79, 0x7e, 0xa7, 0x54, 0x8b, 0x54, 0x8b, 0x80, 0x63, 0x00, 0x00, 0x7c, 0x83, 0x4c, 0x00, 0x7f, 0xb7, 0x3f, 0xb7, 0x54, 0x8b, 0x04, 0x2b, 0x80, 0xec, 0x00, 0x00, 0x7c, 0x83, 0x3c, 0x00, 0x80, 0x14, 0x00, 0x00, 0x80, 0x5c, 0x00, 0x00, 0x54, 0x1f, 0x04, 0x1b, 0x04, 0x23, 0x24, 0x13, 0x54, 0x8b, 0x80, 0xbc, 0x00, 0x00, 0x7c, 0x83, 0x7c, 0x00, 0x7c, 0x00, 0x0c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x64, 0x00, 0x80, 0x5a, 0x00, 0x00, 0x06, 0x6b, 0x14, 0x07, 0x80, 0x8f, 0x00, 0x00, 0x44, 0x27, 0x7c, 0x00, 0x7c, 0x00, 0x64, 0x00, 0x80, 0x3c, 0x00, 0x00, 0x80, 0xaa, 0x00, 0x00, 0x04, 0x07, 0x04, 0x83, 0x54, 0x8b, 0x44, 0x17, 0x14, 0x2f, 0x7c, 0x65, 0x7c, 0x00, 0x34, 0x00, 0x54, 0x8b, 0x34, 0x17, 0x64, 0xb3, 0x7c, 0x00, 0x0c, 0x00, 0x80, 0x6e, 0x00, 0x00, 0x05, 0x03, 0x35, 0x0f, 0x64, 0x8b, 0x34, 0x17, 0x7c, 0x83, 0x7c, 0x00, 0x80, 0xa5, 0x00, 0x00, 0x15, 0x4f, 0x26, 0x1f, 0x64, 0x83, 0x74, 0xa3, 0x7c, 0x00, 0x7c, 0x00, 0x7c, 0x63, 0x81, 0x00, 0x00, 0x34, 0x27, 0x7c, 0x4d, 0x4c, 0x00, 0x80, 0x55, 0x00, 0x00, 0x80, 0x37, 0x00, 0x00, 0x04, 0x0b, 0x04, 0x07, 0x04, 0x0f, 0x04, 0x0b, 0x80, 0x1e, 0x00, 0x00, 0x04, 0x0b, 0x04, 0x07, 0x64, 0x83, 0x7c, 0x00, 0x7c, 0x00, 0x16, 0xef, 0x05, 0x8f, 0x75, 0x97, 0x54, 0x00, 0x7c, 0x00, 0x7c, 0x00, 0x7d, 0x0f, 0x0d, 0x0f, 0x64, 0x3f, }; static mu_Rect default_atlas_icons[] = { [MU_ICON_CHECK] = {0, 0, 18, 18}, [MU_ICON_CLOSE] = {18, 0, 16, 16}, [MU_ICON_COLLAPSED] = {27, 16, 5, 7}, [MU_ICON_EXPANDED] = {0, 18, 7, 5}, [MU_ICON_RESIZE] = {18, 16, 9, 9}, [ATLAS_DIMENSIONS] = {0, 0, 34, 25}, }; mu_Context mu_ctx; mu_Style mu_style = { .font = nil, .size = { 68, 10 }, .padding = 6, .spacing = 4, .indent = 24, .title_height = 26, .scrollbar_size = 12, .thumb_size = 8, .colors = {nil}, }; Image *atlasimage = nil; mu_Rect *atlasicons = default_atlas_icons; u8int defaultcolors[MU_COLOR_MAX][4] = { [MU_COLOR_BG] = {119, 119, 119, 255}, [MU_COLOR_TEXT] = {230, 230, 230, 255}, [MU_COLOR_BORDER] = {25, 25, 25, 255}, [MU_COLOR_WINDOWBG] = {50, 50, 50, 255}, [MU_COLOR_TITLEBG] = {25, 25, 25, 255}, [MU_COLOR_TITLETEXT] = {240, 240, 240, 255}, [MU_COLOR_PANELBG] = {0, 0, 0, 0}, [MU_COLOR_BUTTON] = {75, 75, 75, 255}, [MU_COLOR_BUTTONHOVER] = {95, 95, 95, 255}, [MU_COLOR_BUTTONFOCUS] = {115, 115, 115, 255}, [MU_COLOR_BASE] = {30, 30, 30, 255}, [MU_COLOR_BASEHOVER] = {35, 35, 35, 255}, [MU_COLOR_BASEFOCUS] = {40, 40, 40, 255}, [MU_COLOR_SCROLLBASE] = {43, 43, 43, 255}, [MU_COLOR_SCROLLTHUMB] = {30, 30, 30, 255}, }; static void buffer_grow(void **buf, int onesz, int *bufmax, int bufnum) { while (*bufmax <= bufnum) { *bufmax = MAX(16, *bufmax) * 2; if ((*buf = realloc(*buf, *bufmax*onesz)) == nil) sysfatal("not enough memory for %d items (%d bytes)", *bufmax, *bufmax*onesz); } } 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; } Image * mu_color(u8int r, u8int g, u8int b, u8int a) { Image *c; if ((c = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(r<<24 | g<<16 | b<<8, a))) == nil) sysfatal("couldn't allocate color"); return c; } static Rectangle screenrect(mu_Rect r) { Rectangle rect; rect.min = screen->r.min; rect.min.x += r.x; rect.min.y += r.y; rect.max = rect.min; rect.max.x += r.w; rect.max.y += r.h; return rect; } 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 clip_rect(mu_Rect r1, mu_Rect r2) { int x1 = MAX(r1.x, r2.x); int y1 = MAX(r1.y, r2.y); int x2 = MAX(MIN(r1.x + r1.w, r2.x + r2.w), x1); int y2 = MAX(MIN(r1.y + r1.h, r2.y + r2.h), y1); return mu_rect(x1, y1, x2 - x1, y2 - y1); } static int rect_overlaps_vec2(mu_Rect r, Point 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_Rect rect, int colorid) { mu_draw_rect(rect, mu_style.colors[colorid]); if (colorid == MU_COLOR_SCROLLBASE || colorid == MU_COLOR_SCROLLTHUMB || colorid == MU_COLOR_TITLEBG) return; /* draw border */ mu_draw_box(expand_rect(rect, 1), mu_style.colors[MU_COLOR_BORDER]); } static int text_width(Font *font, const char *text, int len) { return stringnwidth(font, text, len >= 0 ? len : strlen(text)); } void mu_init(void) { Rectangle r; int res, i; if (atlasimage == nil) { r = Rect(0, 0, atlasicons[ATLAS_DIMENSIONS].w, atlasicons[ATLAS_DIMENSIONS].h); atlasimage = allocimage(display, r, RGBA32, 1, DTransparent); if (memcmp(atlasraw, "compressed\n", 11) == 0) res = cloadimage(atlasimage, r, atlasraw+11+5*12, sizeof(atlasraw)-11-5*12); else res = loadimage(atlasimage, r, atlasraw, sizeof(atlasraw)); if (res < 0) sysfatal("failed to load atlas: %r"); } if (mu_style.colors[0] == nil) { for (i = 0; i < MU_COLOR_MAX; i++) { mu_style.colors[i] = mu_color( defaultcolors[i][0], defaultcolors[i][1], defaultcolors[i][2], defaultcolors[i][3] ); } } } void mu_begin(void) { mu_ctx.cmdsnum = mu_ctx.rootnum = 0; mu_ctx.strnum = 0; mu_ctx.scroll_target = nil; mu_ctx.last_hover_root = mu_ctx.hover_root; mu_ctx.hover_root = nil; mu_ctx.mouse_delta.x = mu_ctx.mouse_pos.x - mu_ctx.last_mouse_pos.x; mu_ctx.mouse_delta.y = mu_ctx.mouse_pos.y - mu_ctx.last_mouse_pos.y; } static int compare_zindex(const void *a, const void *b) { return (*(mu_Container**)a)->zindex - (*(mu_Container**)b)->zindex; } void mu_end(void) { int i, n; /* check stacks */ assert(mu_ctx.cntnum == 0); assert(mu_ctx.clipnum == 0); assert(mu_ctx.idsnum == 0); assert(mu_ctx.layoutsnum == 0); /* handle scroll input */ if (mu_ctx.scroll_target) { mu_ctx.scroll_target->scroll.x += mu_ctx.scroll_delta.x; mu_ctx.scroll_target->scroll.y += mu_ctx.scroll_delta.y; } /* unset focus if focus id was not touched this frame */ if (!mu_ctx.updated_focus) mu_ctx.focus = 0; mu_ctx.updated_focus = 0; /* bring hover root to front if mouse was pressed */ if (mu_ctx.mouse_pressed && mu_ctx.hover_root && mu_ctx.hover_root->zindex < mu_ctx.last_zindex) mu_bring_to_front(mu_ctx.hover_root); /* reset input state */ mu_ctx.key_pressed = 0; mu_ctx.text_input[0] = 0; mu_ctx.mouse_pressed = 0; mu_ctx.scroll_delta = ZP; mu_ctx.last_mouse_pos = mu_ctx.mouse_pos; /* sort root containers by zindex */ n = mu_ctx.rootnum; qsort(mu_ctx.root, n, sizeof(*mu_ctx.root), compare_zindex); /* set root container jump commands */ for (i = 0; i < n; i++) { mu_Container *cnt = mu_ctx.root[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_ctx.cmds[0].jump.dst = cnt->head + 1; else mu_ctx.cmds[mu_ctx.root[i - 1]->tail].jump.dst = cnt->head + 1; /* make the last container's tail jump to the end of command list */ if (i == n - 1) mu_ctx.cmds[cnt->tail].jump.dst = mu_ctx.cmdsnum; } } void mu_set_focus(mu_Id id) { mu_ctx.focus = id; mu_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(const void *data, int size) { int idx = mu_ctx.idsnum; mu_Id res = (idx > 0) ? mu_ctx.ids[idx - 1] : HASH_INITIAL; hash(&res, data, size); mu_ctx.last_id = res; return res; } void mu_push_id(const void *data, int size) { buffer_grow(&mu_ctx.ids, sizeof(*mu_ctx.ids), &mu_ctx.idsmax, mu_ctx.idsnum); mu_ctx.ids[mu_ctx.idsnum++] = mu_get_id(data, size); } void mu_pop_id(void) { mu_ctx.idsnum--; } void mu_push_clip_rect(mu_Rect rect) { mu_Rect last = mu_get_clip_rect(); buffer_grow(&mu_ctx.clip, sizeof(*mu_ctx.clip), &mu_ctx.clipmax, mu_ctx.clipnum); mu_ctx.clip[mu_ctx.clipnum++] = clip_rect(rect, last); } void mu_pop_clip_rect(void) { mu_ctx.clipnum--; } mu_Rect mu_get_clip_rect(void) { assert(mu_ctx.clipnum > 0); return mu_ctx.clip[mu_ctx.clipnum - 1]; } int mu_check_clip(mu_Rect r) { mu_Rect cr = mu_get_clip_rect(); 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 MU_CLIP_NONE; return MU_CLIP_PART; } static void push_layout(mu_Rect body, Point scroll) { mu_Layout layout; int width = 0; memset(&layout, 0, sizeof(mu_Layout)); layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h); layout.max = Pt(-0x1000000, -0x1000000); buffer_grow(&mu_ctx.layouts, sizeof(*mu_ctx.layouts), &mu_ctx.layoutsmax, mu_ctx.layoutsnum); mu_ctx.layouts[mu_ctx.layoutsnum++] = layout; mu_layout_row(1, &width, 0); } static mu_Layout * get_layout(void) { return &mu_ctx.layouts[mu_ctx.layoutsnum - 1]; } static void push_container(mu_Container *cnt) { buffer_grow(&mu_ctx.cnt, sizeof(*mu_ctx.cnt), &mu_ctx.cntmax, mu_ctx.cntnum); mu_ctx.cnt[mu_ctx.cntnum++] = cnt; mu_push_id(&cnt, sizeof(mu_Container*)); } static void pop_container(void) { mu_Container *cnt = mu_get_container(); mu_Layout *layout = get_layout(); cnt->content_size.x = layout->max.x - layout->body.x; cnt->content_size.y = layout->max.y - layout->body.y; mu_ctx.cntnum--; mu_ctx.layoutsnum--; mu_pop_id(); } mu_Container * mu_get_container(void) { assert(mu_ctx.cntnum > 0); return mu_ctx.cnt[mu_ctx.cntnum - 1]; } void mu_init_window(mu_Container *cnt, int opt) { memset(cnt, 0, sizeof(*cnt)); cnt->inited = 1; cnt->open = opt & MU_OPT_CLOSED ? 0 : 1; cnt->rect = mu_rect(100, 100, 300, 300); mu_bring_to_front(cnt); } void mu_bring_to_front(mu_Container *cnt) { cnt->zindex = ++mu_ctx.last_zindex; } /*============================================================================ ** input handlers **============================================================================*/ void mu_input_mousemove(int x, int y) { mu_ctx.mouse_pos = Pt(x, y); } void mu_input_mousedown(int x, int y, int btn) { mu_input_mousemove(x, y); mu_ctx.mouse_down |= btn; mu_ctx.mouse_pressed |= btn; } void mu_input_mouseup(int x, int y, int btn) { mu_input_mousemove(x, y); mu_ctx.mouse_down &= ~btn; } void mu_input_scroll(int x, int y) { mu_ctx.scroll_delta.x += x; mu_ctx.scroll_delta.y += y; } void mu_input_keydown(int key) { mu_ctx.key_pressed |= key; mu_ctx.key_down |= key; } void mu_input_keyup(int key) { mu_ctx.key_down &= ~key; } void mu_input_text(const char *text) { int len = strlen(mu_ctx.text_input); int size = strlen(text) + 1; assert(len + size <= (int) sizeof(mu_ctx.text_input)); memcpy(mu_ctx.text_input + len, text, size); } /*============================================================================ ** commandlist **============================================================================*/ mu_Command * mu_push_command(int type) { mu_Command *cmd; buffer_grow(&mu_ctx.cmds, sizeof(mu_Command), &mu_ctx.cmdsmax, mu_ctx.cmdsnum); cmd = &mu_ctx.cmds[mu_ctx.cmdsnum++]; memset(cmd, 0, sizeof(*cmd)); cmd->type = type; return cmd; } static int push_jump(int dst) { mu_Command *cmd; cmd = mu_push_command(MU_COMMAND_JUMP); cmd->jump.dst = dst; return mu_ctx.cmdsnum-1; } void mu_set_clip(mu_Rect rect) { mu_Command *cmd; cmd = mu_push_command(MU_COMMAND_CLIP); cmd->clip.rect = rect; } void mu_draw_rect(mu_Rect rect, Image *color) { mu_Command *cmd; rect = clip_rect(rect, mu_get_clip_rect()); if (rect.w > 0 && rect.h > 0) { cmd = mu_push_command(MU_COMMAND_RECT); cmd->rect.rect = rect; cmd->rect.color = color; } } void mu_draw_box(mu_Rect rect, Image *color) { mu_draw_rect(mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color); mu_draw_rect(mu_rect(rect.x+1, rect.y + rect.h-1, rect.w-2, 1), color); mu_draw_rect(mu_rect(rect.x, rect.y, 1, rect.h), color); mu_draw_rect(mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color); } void mu_draw_text(Font *font, const char *s, int len, Point pos, Image *color) { mu_Command *cmd; mu_Rect rect; int clipped; if (len < 0) len = strlen(s); rect = mu_rect(pos.x, pos.y, text_width(font, s, len), font->height); clipped = mu_check_clip(rect); if (clipped == MU_CLIP_ALL ) return; if (clipped == MU_CLIP_PART) mu_set_clip(mu_get_clip_rect()); if (mu_ctx.strmax <= mu_ctx.strnum+len+1) { mu_ctx.strmax = MAX(mu_ctx.strmax, mu_ctx.strnum+len+1) * 2; if ((mu_ctx.str = realloc(mu_ctx.str, mu_ctx.strmax)) == nil) sysfatal("not enough memory for %d chars", mu_ctx.strmax); } cmd = mu_push_command(MU_COMMAND_TEXT); cmd->text.s = mu_ctx.strnum; memmove(mu_ctx.str+mu_ctx.strnum, s, len); mu_ctx.strnum += len; mu_ctx.str[mu_ctx.strnum++] = 0; cmd->text.pos = pos; cmd->text.color = color; cmd->text.font = font; /* reset clipping if it was set */ if (clipped) mu_set_clip(unclipped_rect); } void mu_draw_icon(int id, mu_Rect rect) { mu_Command *cmd; /* do clip command if the rect isn't fully contained within the cliprect */ int clipped = mu_check_clip(rect); if (clipped == MU_CLIP_ALL ) return; if (clipped == MU_CLIP_PART) mu_set_clip(mu_get_clip_rect()); /* do icon command */ cmd = mu_push_command(MU_COMMAND_ICON); cmd->icon.id = id; cmd->icon.rect = rect; /* reset clipping if it was set */ if (clipped) mu_set_clip(unclipped_rect); } /*============================================================================ ** layout **============================================================================*/ enum { RELATIVE = 1, ABSOLUTE, }; void mu_layout_begin_column(void) { push_layout(mu_layout_next(), ZP); } void mu_layout_end_column(void) { mu_Layout *a, *b; b = get_layout(); mu_ctx.layoutsnum--; /* inherit position/next_row/max from child layout if they are greater */ a = get_layout(); a->position.x = MAX(a->position.x, b->position.x + b->body.x - a->body.x); a->next_row = MAX(a->next_row, b->next_row + b->body.y - a->body.y); a->max.x = MAX(a->max.x, b->max.x); a->max.y = MAX(a->max.y, b->max.y); } void mu_layout_row(int items, const int *widths, int height) { mu_Layout *layout = get_layout(); if (widths) { assert(items <= MU_MAX_WIDTHS); memcpy(layout->widths, widths, items * sizeof(widths[0])); } layout->items = items; layout->position = Pt(layout->indent, layout->next_row); layout->size.y = height; layout->row_index = 0; } void mu_layout_width(int width) { get_layout()->size.x = width; } void mu_layout_height(int height) { get_layout()->size.y = height; } void mu_layout_set_next(mu_Rect r, int relative) { mu_Layout *layout = get_layout(); layout->next = r; layout->next_type = relative ? RELATIVE : ABSOLUTE; } mu_Rect mu_layout_next(void) { mu_Layout *layout = get_layout(); 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) { mu_ctx.last_rect = res; return res; } } else { /* handle next row */ if (layout->row_index == layout->items) mu_layout_row(layout->items, nil, layout->size.y); /* position */ res.x = layout->position.x; res.y = layout->position.y; /* size */ res.w = layout->items > -1 ? layout->widths[layout->row_index] : layout->size.x; res.h = layout->size.y; if (res.w == 0) res.w = mu_style.size.x + mu_style.padding * 2; if (res.h == 0) res.h = mu_style.size.y + mu_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->row_index++; } /* update position */ layout->position.x += res.w + mu_style.spacing; layout->next_row = MAX(layout->next_row, res.y + res.h + mu_style.spacing); /* apply body offset */ res.x += layout->body.x; res.y += layout->body.y; /* update max position */ layout->max.x = MAX(layout->max.x, res.x + res.w); layout->max.y = MAX(layout->max.y, res.y + res.h); mu_ctx.last_rect = res; return res; } /*============================================================================ ** controls **============================================================================*/ static int in_hover_root(void) { int i = mu_ctx.cntnum; while (i--) { if (mu_ctx.cnt[i] == mu_ctx.last_hover_root) return 1; /* only root containers have their `head` field set; stop searching if we've ** reached the current root container */ if (mu_ctx.cnt[i]->head >= 0) break; } return 0; } void mu_draw_control_frame(mu_Id id, mu_Rect rect, int colorid, int opt) { if (opt & MU_OPT_NOFRAME) return; colorid += (mu_ctx.focus == id) ? 2 : (mu_ctx.hover == id) ? 1 : 0; draw_frame(rect, colorid); } void mu_draw_control_text(const char *str, mu_Rect rect, int colorid, int opt) { Point pos; Font *font = mu_style.font; int tw = text_width(font, str, -1); mu_push_clip_rect(rect); pos.y = rect.y + (rect.h - font->height) / 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 - mu_style.padding; else pos.x = rect.x + mu_style.padding; mu_draw_text(font, str, -1, pos, mu_style.colors[colorid]); mu_pop_clip_rect(); } int mu_mouse_over(mu_Rect rect) { return rect_overlaps_vec2(rect, mu_ctx.mouse_pos) && rect_overlaps_vec2(mu_get_clip_rect(), mu_ctx.mouse_pos) && in_hover_root(); } void mu_update_control(mu_Id id, mu_Rect rect, int opt) { int mouseover = mu_mouse_over(rect); if (mu_ctx.focus == id) mu_ctx.updated_focus = 1; if (opt & MU_OPT_NOINTERACT) return; if (mouseover && !mu_ctx.mouse_down) mu_ctx.hover = id; if (mu_ctx.focus == id) { if (mu_ctx.mouse_pressed && !mouseover) mu_set_focus(0); if (!mu_ctx.mouse_down && ~opt & MU_OPT_HOLDFOCUS) mu_set_focus(0); } if (mu_ctx.hover == id) { if (!mouseover) mu_ctx.hover = 0; else if (mu_ctx.mouse_pressed) mu_set_focus(id); } } void mu_text(const char *text) { const char *start, *end, *p = text; int width = -1; Font *font = mu_style.font; Image *color = mu_style.colors[MU_COLOR_TEXT]; mu_layout_begin_column(); mu_layout_row(1, &width, font->height); do { mu_Rect r = mu_layout_next(); int w = 0; start = end = p; do { const char* word = p; while (*p && *p != ' ' && *p != '\n') p++; w += text_width(font, word, p - word); if (w > r.w && end != start) break; w += text_width(font, p, 1); end = p++; } while (*end && *end != '\n'); mu_draw_text(font, start, end - start, Pt(r.x, r.y), color); p = end + 1; } while (*end); mu_layout_end_column(); } void mu_label(const char *text) { mu_draw_control_text(text, mu_layout_next(), MU_COLOR_TEXT, 0); } int mu_button_ex(const char *label, int icon, int opt) { int res = 0; mu_Id id = label ? mu_get_id(label, strlen(label)) : mu_get_id(&icon, sizeof(icon)); mu_Rect r = mu_layout_next(); mu_update_control(id, r, opt); /* handle click */ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.focus == id) res |= MU_RES_SUBMIT; /* draw */ mu_draw_control_frame(id, r, MU_COLOR_BUTTON, opt); if (label) mu_draw_control_text(label, r, MU_COLOR_TEXT, opt); if (icon) mu_draw_icon(icon, r); return res; } int mu_button(const char *label) { return mu_button_ex(label, 0, MU_OPT_ALIGNCENTER); } int mu_checkbox(int *state, const char *label) { int res = 0; mu_Id id = mu_get_id(&state, sizeof(state)); mu_Rect r = mu_layout_next(); mu_Rect box = mu_rect(r.x, r.y, r.h, r.h); mu_update_control(id, r, 0); /* handle click */ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.focus == id) { res |= MU_RES_CHANGE; *state = !*state; } /* draw */ mu_draw_control_frame(id, box, MU_COLOR_BASE, 0); if (*state) mu_draw_icon(MU_ICON_CHECK, box); r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h); mu_draw_control_text(label, r, MU_COLOR_TEXT, 0); return res; } int mu_textbox_raw(char *buf, int bufsz, mu_Id id, mu_Rect r, int opt) { int res = 0; mu_update_control(id, r, opt | MU_OPT_HOLDFOCUS); if (mu_ctx.focus == id) { /* handle text input */ int len = strlen(buf); int n = MIN(bufsz - len - 1, (int)strlen(mu_ctx.text_input)); if (n > 0) { memcpy(buf + len, mu_ctx.text_input, n); len += n; buf[len] = '\0'; res |= MU_RES_CHANGE; } /* handle backspace */ if (mu_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; } if (mu_ctx.key_pressed & MU_KEY_NACK && len > 0) { buf[0] = '\0'; res |= MU_RES_CHANGE; } /* handle return */ if (mu_ctx.key_pressed & MU_KEY_RETURN) { mu_set_focus(0); res |= MU_RES_SUBMIT; } } /* draw */ mu_draw_control_frame(id, r, MU_COLOR_BASE, opt); if (mu_ctx.focus == id) { Image *color = mu_style.colors[MU_COLOR_TEXT]; Font *font = mu_style.font; int textw = text_width(font, buf, -1); int texth = font->height; int ofx = r.w - mu_style.padding - textw - 1; int textx = r.x + MIN(ofx, mu_style.padding); int texty = r.y + (r.h - texth) / 2; mu_push_clip_rect(r); mu_draw_text(font, buf, -1, Pt(textx, texty), color); mu_draw_rect(mu_rect(textx + textw, texty, 1, texth), color); mu_pop_clip_rect(); } else { mu_draw_control_text(buf, r, MU_COLOR_TEXT, opt); } return res; } static int number_textbox(double *value, mu_Rect r, mu_Id id) { if (((mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.key_down & MU_KEY_SHIFT) || mu_ctx.mouse_pressed == MU_MOUSE_RIGHT) && mu_ctx.hover == id) { mu_ctx.number_editing = id; sprint(mu_ctx.number_buf, MU_REAL_FMT, *value); } if (mu_ctx.number_editing == id) { int res = mu_textbox_raw(mu_ctx.number_buf, sizeof(mu_ctx.number_buf), id, r, 0); if (res & MU_RES_SUBMIT || mu_ctx.focus != id) { *value = strtod(mu_ctx.number_buf, nil); mu_ctx.number_editing = 0; } else { return 1; } } return 0; } int mu_textbox_ex(char *buf, int bufsz, int opt) { mu_Id id = mu_get_id(&buf, sizeof(buf)); mu_Rect r = mu_layout_next(); return mu_textbox_raw(buf, bufsz, id, r, opt); } int mu_textbox(char *buf, int bufsz) { return mu_textbox_ex(buf, bufsz, 0); } int mu_slider_ex(double *value, double low, double high, double step, const char *fmt, int opt) { char buf[MU_MAX_FMT + 1]; mu_Rect thumb; int w, res = 0; double normalized, last = *value, v = last; mu_Id id = mu_get_id(&value, sizeof(value)); mu_Rect base = mu_layout_next(); /* handle text input mode */ if (number_textbox(&v, base, id)) return res; /* handle normal mode */ mu_update_control(id, base, opt); /* handle input */ if (mu_ctx.focus == id) { if (mu_ctx.mouse_down == MU_MOUSE_LEFT) v = low + ((double)(mu_ctx.mouse_pos.x - base.x) / base.w) * (high - low); } else if (mu_ctx.hover == id) { if ((mu_ctx.key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) == MU_KEY_LEFT) { v -= step ? step : 1; if (v < low) v = low; } else if ((mu_ctx.key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) == MU_KEY_RIGHT) { v += step ? step : 1; if (v > high) v = high; } } if (step) v = ((long)((v + (v > 0 ? step/2 : (-step/2))) / step)) * step; /* clamp and store value, update res */ *value = v = CLAMP(v, low, high); if (last != v) res |= MU_RES_CHANGE; /* draw base */ mu_draw_control_frame(id, base, MU_COLOR_BASE, opt); /* draw thumb */ w = mu_style.thumb_size; normalized = (v - low) / (high - low); thumb = mu_rect(base.x + normalized * (base.w - w), base.y, w, base.h); mu_draw_control_frame(id, thumb, MU_COLOR_BUTTON, opt); /* draw text */ sprint(buf, fmt, v); mu_draw_control_text(buf, base, MU_COLOR_TEXT, opt); return res; } int mu_slider(double *value, double low, double high) { return mu_slider_ex(value, low, high, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER); } int mu_number_ex(double *value, double step, const char *fmt, int opt) { char buf[MU_MAX_FMT + 1]; int res = 0; mu_Id id = mu_get_id(&value, sizeof(value)); mu_Rect base = mu_layout_next(); double last = *value; /* handle text input mode */ if (number_textbox(value, base, id)) return res; /* handle normal mode */ mu_update_control(id, base, opt); /* handle input */ if (mu_ctx.focus == id && mu_ctx.mouse_down == MU_MOUSE_LEFT) *value += mu_ctx.mouse_delta.x * step; /* set flag if value changed */ if (*value != last) res |= MU_RES_CHANGE; /* draw base */ mu_draw_control_frame(id, base, MU_COLOR_BASE, opt); /* draw text */ sprint(buf, fmt, *value); mu_draw_control_text(buf, base, MU_COLOR_TEXT, opt); return res; } int mu_number(double *value, double step) { return mu_number_ex(value, step, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER); } static int header(int *state, const char *label, int istreenode) { mu_Rect r; mu_Id id; int width = -1; mu_layout_row(1, &width, 0); r = mu_layout_next(); id = mu_get_id(&state, sizeof(state)); mu_update_control(id, r, 0); /* handle click */ if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && mu_ctx.focus == id) *state = !(*state); /* draw */ if (istreenode) { if (mu_ctx.hover == id) draw_frame(r, MU_COLOR_BUTTONHOVER); } else { mu_draw_control_frame(id, r, MU_COLOR_BUTTON, 0); } mu_draw_icon( *state ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED, mu_rect(r.x, r.y, r.h, r.h) ); r.x += r.h - mu_style.padding; r.w -= r.h - mu_style.padding; mu_draw_control_text(label, r, MU_COLOR_TEXT, 0); return *state ? MU_RES_ACTIVE : 0; } int mu_header(int *state, const char *label) { return header(state, label, 0); } int mu_begin_treenode(int *state, const char *label) { int res = header(state, label, 1); if (res & MU_RES_ACTIVE) { get_layout()->indent += mu_style.indent; mu_push_id(&state, sizeof(void*)); } return res; } void mu_end_treenode(void) { get_layout()->indent -= mu_style.indent; mu_pop_id(); } static void scrollbar(mu_Container *cnt, mu_Rect *b, Point cs, int v) { /* only add scrollbar if content size is larger than body */ int maxscroll = v ? cs.y - b->h : cs.x - b->w; if (maxscroll > 0 && (v ? b->h : b->x) > 0) { mu_Rect base, thumb; mu_Id id = mu_get_id(v ? "!scrollbary" : "!scrollbarx", 11); /* get sizing / positioning */ base = *b; if (v) { base.x = b->x + b->w; base.w = mu_style.scrollbar_size; } else { base.y = b->y + b->h; base.h = mu_style.scrollbar_size; } /* handle input */ mu_update_control(id, base, 0); if (mu_ctx.focus == id && mu_ctx.mouse_down == MU_MOUSE_LEFT) { if (v) cnt->scroll.y += mu_ctx.mouse_delta.y * cs.y / base.h; else cnt->scroll.x += mu_ctx.mouse_delta.x * cs.x / base.w; } /* clamp scroll to limits */ if (v) cnt->scroll.y = CLAMP(cnt->scroll.y, 0, maxscroll); else cnt->scroll.x = CLAMP(cnt->scroll.x, 0, maxscroll); /* draw base and thumb */ draw_frame(base, MU_COLOR_SCROLLBASE); thumb = base; if (v) { thumb.h = MAX(mu_style.thumb_size, base.h * b->h / cs.y); thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll; } else { thumb.w = MAX(mu_style.thumb_size, base.w * b->w / cs.x); thumb.x += cnt->scroll.x * (base.w - thumb.w) / maxscroll; } draw_frame(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(*b)) mu_ctx.scroll_target = cnt; } else if (v) { cnt->scroll.y = 0; } else { cnt->scroll.x = 0; } } static void scrollbars(mu_Container *cnt, mu_Rect *body) { int sz = mu_style.scrollbar_size; Point cs = cnt->content_size; cs.x += mu_style.padding * 2; cs.y += mu_style.padding * 2; mu_push_clip_rect(*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(cnt, body, cs, 1); scrollbar(cnt, body, cs, 0); mu_pop_clip_rect(); } static void push_container_body(mu_Container *cnt, mu_Rect body, int opt) { if (~opt & MU_OPT_NOSCROLL) scrollbars(cnt, &body); push_layout(expand_rect(body, -mu_style.padding), cnt->scroll); cnt->body = body; } static void begin_root_container(mu_Container *cnt) { push_container(cnt); /* push container to roots list and push head command */ buffer_grow(&mu_ctx.root, sizeof(*mu_ctx.root), &mu_ctx.rootmax, mu_ctx.rootnum); mu_ctx.root[mu_ctx.rootnum++] = cnt; cnt->head = push_jump(-1); /* 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, mu_ctx.mouse_pos) && (!mu_ctx.hover_root || cnt->zindex > mu_ctx.hover_root->zindex)) mu_ctx.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 */ buffer_grow(&mu_ctx.clip, sizeof(*mu_ctx.clip), &mu_ctx.clipmax, mu_ctx.clipnum); mu_ctx.clip[mu_ctx.clipnum++] = unclipped_rect; } static void end_root_container(void) { /* 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_container(); cnt->tail = push_jump(-1); mu_ctx.cmds[cnt->head].jump.dst = mu_ctx.cmdsnum; /* pop base clip rect and container */ mu_pop_clip_rect(); pop_container(); } int mu_begin_window_ex(mu_Container *cnt, const char *title, int opt) { mu_Rect rect, body, titlerect; if (!cnt->inited) mu_init_window(cnt, opt); if (!cnt->open) return 0; begin_root_container(cnt); rect = cnt->rect; body = rect; /* draw frame */ if (~opt & MU_OPT_NOFRAME) draw_frame(rect, MU_COLOR_WINDOWBG); /* moving all windows by "dragging" background */ if (mu_ctx.last_hover_root == nil && mu_ctx.hover_root == nil && mu_ctx.mouse_pressed == MU_MOUSE_LEFT) mu_ctx.moving = 1; else if (mu_ctx.mouse_down != MU_MOUSE_LEFT) mu_ctx.moving = 0; if (mu_ctx.moving) { cnt->rect.x += mu_ctx.mouse_delta.x; cnt->rect.y += mu_ctx.mouse_delta.y; } /* do title bar */ titlerect = rect; titlerect.h = mu_style.title_height; if (~opt & MU_OPT_NOTITLE) { mu_Id id = mu_get_id("!title", 6); draw_frame(titlerect, MU_COLOR_TITLEBG); /* do title text */ mu_update_control(id, titlerect, opt); mu_draw_control_text(title, titlerect, MU_COLOR_TITLETEXT, opt); if (id == mu_ctx.focus && mu_ctx.mouse_down == MU_MOUSE_LEFT) { cnt->rect.x += mu_ctx.mouse_delta.x; cnt->rect.y += mu_ctx.mouse_delta.y; } body.y += titlerect.h; body.h -= titlerect.h; /* do `close` button */ if (~opt & MU_OPT_NOCLOSE) { mu_Id id = mu_get_id("!close", 6); mu_Rect r = mu_rect( titlerect.x + titlerect.w - titlerect.h, titlerect.y, titlerect.h, titlerect.h ); titlerect.w -= r.w; mu_draw_icon(MU_ICON_CLOSE, r); mu_update_control(id, r, opt); if (mu_ctx.mouse_pressed == MU_MOUSE_LEFT && id == mu_ctx.focus) cnt->open = 0; } } push_container_body(cnt, body, opt); /* do `resize` handle */ if (~opt & MU_OPT_NORESIZE) { int sz = mu_style.scrollbar_size; mu_Id id = mu_get_id("!resize", 7); mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz); mu_update_control(id, r, opt); mu_draw_icon(MU_ICON_RESIZE, r); if (id == mu_ctx.focus && mu_ctx.mouse_down == MU_MOUSE_LEFT) { cnt->rect.w = MAX(96, cnt->rect.w + mu_ctx.mouse_delta.x); cnt->rect.h = MAX(64, cnt->rect.h + mu_ctx.mouse_delta.y); } } /* resize to content size */ if (opt & MU_OPT_AUTOSIZE) { mu_Rect r = get_layout()->body; if (opt & MU_OPT_AUTOSIZE_W) cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w); if (opt & MU_OPT_AUTOSIZE_H) 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 && mu_ctx.mouse_pressed && mu_ctx.last_hover_root != cnt) cnt->open = 0; mu_push_clip_rect(cnt->body); return MU_RES_ACTIVE; } int mu_begin_window(mu_Container *cnt, const char *title) { return mu_begin_window_ex(cnt, title, 0); } void mu_end_window(void) { mu_pop_clip_rect(); end_root_container(); } void mu_open_popup(mu_Container *cnt) { /* set as hover root so popup isn't closed in begin_window_ex() */ mu_ctx.last_hover_root = mu_ctx.hover_root = cnt; /* init container if not inited */ if (!cnt->inited) mu_init_window(cnt, 0); /* position at mouse cursor, open and bring-to-front */ cnt->rect = mu_rect(mu_ctx.mouse_pos.x, mu_ctx.mouse_pos.y, 0, 0); cnt->open = 1; mu_bring_to_front(cnt); } int mu_begin_popup(mu_Container *cnt) { return mu_begin_window_ex(cnt, "", MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE | MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED); } void mu_end_popup(void) { mu_end_window(); } void mu_begin_panel_ex(mu_Container *cnt, int opt) { cnt->rect = mu_layout_next(); if (~opt & MU_OPT_NOFRAME) draw_frame(cnt->rect, MU_COLOR_PANELBG); push_container(cnt); push_container_body(cnt, cnt->rect, opt); mu_push_clip_rect(cnt->body); } void mu_begin_panel(mu_Container *cnt) { mu_begin_panel_ex(cnt, 0); } void mu_end_panel(void) { mu_pop_clip_rect(); pop_container(); } int mu_render(void) { mu_Command *cmd; mu_Rect r, iconr; if (memcmp(&mu_ctx.screen, &screen->r, sizeof(mu_ctx.screen)) != 0) mu_ctx.screen = screen->r; else if (mu_ctx.oldcmdsnum == mu_ctx.cmdsnum && memcmp(mu_ctx.oldcmds, mu_ctx.cmds, mu_ctx.cmdsnum*sizeof(mu_Command)) == 0) if (mu_ctx.oldstrnum == mu_ctx.strnum && memcmp(mu_ctx.oldstr, mu_ctx.str, mu_ctx.strnum) == 0) return 0; if (mu_ctx.oldcmdsmax != mu_ctx.cmdsmax && (mu_ctx.oldcmds = realloc(mu_ctx.oldcmds, mu_ctx.cmdsmax*sizeof(mu_Command))) == nil) sysfatal("couldn't allocate memory for old cmds"); mu_ctx.oldcmdsmax = mu_ctx.cmdsmax; mu_ctx.oldcmdsnum = mu_ctx.cmdsnum; memmove(mu_ctx.oldcmds, mu_ctx.cmds, mu_ctx.cmdsnum*sizeof(mu_Command)); if (mu_ctx.oldstrmax != mu_ctx.strmax && (mu_ctx.oldstr = realloc(mu_ctx.oldstr, mu_ctx.strmax)) == nil) sysfatal("couldn't allocate memory for old strings"); mu_ctx.oldstrmax = mu_ctx.strmax; mu_ctx.oldstrnum = mu_ctx.strnum; memmove(mu_ctx.oldstr, mu_ctx.str, mu_ctx.strnum); draw(screen, screen->r, mu_style.colors[MU_COLOR_BG], nil, ZP); for (cmd = mu_ctx.cmds; cmd < mu_ctx.cmds + mu_ctx.cmdsnum;) { switch (cmd->type) { case MU_COMMAND_TEXT: if (cmd->text.color != nil) string(screen, addpt(screen->r.min, cmd->text.pos), cmd->text.color, ZP, mu_style.font, mu_ctx.str+cmd->text.s); break; case MU_COMMAND_RECT: if (cmd->rect.color != nil) draw(screen, screenrect(cmd->rect.rect), cmd->rect.color, nil, ZP); break; case MU_COMMAND_ICON: r = cmd->icon.rect; iconr = atlasicons[cmd->icon.id]; r.x += (r.w - iconr.w) / 2; r.y += (r.h - iconr.h) / 2; r.w = iconr.w; r.h = iconr.h; draw(screen, screenrect(r), atlasimage, nil, Pt(iconr.x, iconr.y)); break; case MU_COMMAND_CLIP: replclipr(screen, 0, screenrect(cmd->clip.rect)); break; case MU_COMMAND_JUMP: if (cmd->jump.dst < 0) return 1; cmd = &mu_ctx.cmds[cmd->jump.dst]; continue; } cmd++; } return 1; }