ref: f0f44fdb991b775ca0e10ac68cad0a94114ef93d
dir: /src/microui.c/
/* ** Copyright (c) 2018 rxi ** ** Permission is hereby granted, free of charge, to any person obtaining a copy ** of this software and associated documentation files (the "Software"), to ** deal in the Software without restriction, including without limitation the ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ** sell copies of the Software, and to permit persons to whom the Software is ** furnished to do so, subject to the following conditions: ** ** The above copyright notice and this permission notice shall be included in ** all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS ** IN THE SOFTWARE. */ #include <stdio.h> #include <string.h> #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++; \ } 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 = { NULL, /* font */ { 68, 10 }, /* size */ 6, 4, 24, /* padding, spacing, indent */ 26, 20, /* title_height, footer_height */ 12, 8, /* scrollbar_size, thumb_size */ { { 230, 230, 230, 255 }, /* MU_COLOR_TEXT */ { 20, 20, 20, 200 }, /* MU_COLOR_BORDER */ { 50, 50, 50, 255 }, /* MU_COLOR_WINDOWBG */ { 20, 20, 20, 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 */ { 30, 30, 30, 255 }, /* MU_COLOR_SCROLLBASE */ { 80, 80, 80, 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 clip_rect(mu_Rect rect, mu_Rect clip) { int x1 = rect.x + rect.w; int y1 = rect.y + rect.h; int x2 = clip.x + clip.w; int y2 = clip.y + clip.h; rect.x = mu_max(rect.x, clip.x); rect.y = mu_max(rect.y, clip.y); rect.w = mu_min(x1, x2) - rect.x; rect.h = mu_min(y1, y2) - rect.y; rect.w = mu_max(0, rect.w); rect.h = mu_max(0, rect.h); return rect; } 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 int text_width(mu_Font font, const char *str, int len) { unused(font); if (len < 0) { len = strlen(str); } return len * 8; } static int text_height(mu_Font font) { unused(font); return 16; } 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->text_width = text_width; ctx->text_height = text_height; ctx->draw_frame = draw_frame; ctx->_style = default_style; ctx->style = &ctx->_style; } void mu_begin(mu_Context *ctx) { ctx->command_list.idx = 0; ctx->root_list.idx = 0; ctx->scroll_target = NULL; ctx->last_hover_root = ctx->hover_root; ctx->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; } 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 mouse wheel scrolling */ if (ctx->scroll_target && ctx->mouse_wheel) { ctx->scroll_target->scroll.y -= ctx->mouse_wheel * 30; } /* 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->hover_root && ctx->hover_root->zindex < ctx->last_zindex) { mu_bring_to_front(ctx, ctx->hover_root); } /* reset input state */ ctx->key_pressed = 0; ctx->text_input[0] = '\0'; ctx->mouse_pressed = 0; ctx->mouse_wheel = 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 MU_HASH_SEED 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] : MU_HASH_SEED; 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, clip_rect(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 MU_CLIP_NONE; } 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(mu_Layout)); layout.body = body; layout.body.x -= scroll.x; layout.body.y -= scroll.y; 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 push_container(mu_Context *ctx, mu_Container *cnt) { push(ctx->container_stack, cnt); mu_push_id(ctx, &cnt, sizeof(mu_Container*)); cnt->body = cnt->rect; } static void pop_container(mu_Context *ctx) { mu_Container *cnt = mu_get_container(ctx); mu_Layout *layout = get_layout(ctx); cnt->last_body = cnt->body; 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_container(mu_Context *ctx) { expect(ctx->container_stack.idx > 0); return ctx->container_stack.items[ ctx->container_stack.idx - 1 ]; } void mu_init_window(mu_Context *ctx, 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(ctx, cnt); } void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt) { cnt->zindex = ++ctx->last_zindex; } /*============================================================================ ** 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_mousewheel(mu_Context *ctx, int y) { ctx->mouse_wheel += 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->text_input); int size = strlen(text) + 1; expect(len + size <= (int) sizeof(ctx->text_input)); memcpy(ctx->text_input + 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 = clip_rect(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->row_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 res; } } else { /* handle next row */ if (layout->row_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 > -1 ? layout->widths[layout->row_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->row_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->last_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 (!mouseover) { ctx->hover = 0; } else if (ctx->mouse_pressed) { mu_set_focus(ctx, id); } } } 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_button(mu_Context *ctx, const char *label) { return mu_button_ex(ctx, label, 0, MU_OPT_ALIGNCENTER); } int mu_checkbox(mu_Context *ctx, int *state, const char *label) { 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->text_input)); if (n > 0) { memcpy(buf + len, ctx->text_input, 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_editing = id; sprintf(ctx->number_buf, MU_REAL_FMT, *value); } if (ctx->number_editing == id) { int res = mu_textbox_raw( ctx, ctx->number_buf, sizeof(ctx->number_buf), id, r, 0); if (res & MU_RES_SUBMIT || ctx->focus != id) { *value = strtod(ctx->number_buf, NULL); ctx->number_editing = 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_textbox(mu_Context *ctx, char *buf, int bufsz) { return mu_textbox_ex(ctx, buf, bufsz, 0); } 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 w, res = 0; mu_Real normalized, 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 == MU_MOUSE_LEFT) { v = low + ((mu_Real) (ctx->mouse_pos.x - base.x) / base.w) * (high - low); if (step) { v = ((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; normalized = (v - low) / (high - low); thumb = mu_rect(base.x + normalized * (base.w - w), 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_slider(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high) { return mu_slider_ex(ctx, value, low, high, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER); } 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; } int mu_number(mu_Context *ctx, mu_Real *value, mu_Real step) { return mu_number_ex(ctx, value, step, "%.03f", MU_OPT_ALIGNCENTER); } static int header(mu_Context *ctx, int *state, const char *label, int istreenode) { mu_Rect r; mu_Id id; int width = -1; mu_layout_row(ctx, 1, &width, 0); r = mu_layout_next(ctx); id = mu_get_id(ctx, &state, sizeof(state)); mu_update_control(ctx, id, r, 0); /* handle click */ if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { *state = !(*state); } /* 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, *state ? 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 *state ? MU_RES_ACTIVE : 0; } int mu_header(mu_Context *ctx, int *state, const char *label) { return header(ctx, state, label, 0); } int mu_begin_treenode(mu_Context *ctx, int *state, const char *label) { int res = header(ctx, state, label, 1); if (res & MU_RES_ACTIVE) { get_layout(ctx)->indent += ctx->style->indent; mu_push_id(ctx, &state, sizeof(void*)); } 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(16, 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) { 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, cnt->body); /* resize body to make room for scrollbars */ if (cs.y > cnt->last_body.h) { cnt->body.w -= sz; } if (cs.x > cnt->last_body.w) { cnt->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, cnt->body, cs, x, y, w, h); scrollbar(ctx, cnt, cnt->body, cs, y, x, h, w); mu_pop_clip_rect(ctx); } static void push_container_body(mu_Context *ctx, mu_Container *cnt, int opt) { if (~opt & MU_OPT_NOSCROLL) { scrollbars(ctx, cnt); } mu_push_clip_rect(ctx, cnt->body); push_layout(ctx, expand_rect(cnt->body, -ctx->style->padding), cnt->scroll); } static void begin_root_container(mu_Context *ctx, mu_Container *cnt) { push_container(ctx, 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->hover_root || cnt->zindex > ctx->hover_root->zindex)) { ctx->hover_root = cnt; } /* clipping is set directly 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_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, mu_Container *cnt, const char *title, int opt) { mu_Rect rect, titlerect; if (!cnt->inited) { mu_init_window(ctx, cnt, opt); } if (!cnt->open) { return 0; } begin_root_container(ctx, cnt); /* draw frame */ if (~opt & MU_OPT_NOFRAME) { ctx->draw_frame(ctx, cnt->rect, MU_COLOR_WINDOWBG); } /* do title bar */ rect = cnt->rect; titlerect = rect; titlerect.h = ctx->style->title_height; if (~opt & MU_OPT_NOTITLE) { ctx->draw_frame(ctx, titlerect, 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, titlerect, opt); mu_draw_control_text(ctx, title, titlerect, MU_COLOR_TITLETEXT, 0); if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { cnt->rect.x += ctx->mouse_delta.x; cnt->rect.y += ctx->mouse_delta.y; } cnt->body.y += titlerect.h; cnt->body.h -= titlerect.h; } /* do `close` button */ if (~opt & MU_OPT_NOCLOSE) { mu_Id id = mu_get_id(ctx, "!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(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; } } } /* do `resize` notch */ if (~opt & MU_OPT_NORESIZE) { int sz = ctx->style->footer_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); mu_draw_icon(ctx, MU_ICON_RESIZE, r, ctx->style->colors[MU_COLOR_TEXT]); if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { cnt->rect.w += ctx->mouse_delta.x; cnt->rect.h += ctx->mouse_delta.y; cnt->rect.w = mu_max(96, cnt->rect.w); cnt->rect.h = mu_max(64, cnt->rect.h); } cnt->body.h -= sz; } /* do scrollbars and init clipping */ push_container_body(ctx, cnt, opt); /* 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->last_hover_root != cnt) { cnt->open = 0; } return MU_RES_ACTIVE; } int mu_begin_window(mu_Context *ctx, mu_Container *cnt, const char *title) { return mu_begin_window_ex(ctx, cnt, title, 0); } void mu_end_window(mu_Context *ctx) { mu_pop_clip_rect(ctx); end_root_container(ctx); } void mu_open_popup(mu_Context *ctx, mu_Container *cnt) { /* set as hover root so popup isn't closed in begin_window_ex() */ ctx->last_hover_root = ctx->hover_root = cnt; /* init container if not inited */ if (!cnt->inited) { mu_init_window(ctx, cnt, 0); } /* position at mouse cursor, open and bring-to-front */ cnt->rect = mu_rect(ctx->mouse_pos.x, ctx->mouse_pos.y, 0, 0); cnt->open = 1; mu_bring_to_front(ctx, cnt); } int mu_begin_popup(mu_Context *ctx, mu_Container *cnt) { 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, cnt, "", opt); } void mu_end_popup(mu_Context *ctx) { mu_end_window(ctx); } void mu_begin_panel_ex(mu_Context *ctx, mu_Container *cnt, int opt) { cnt->rect = mu_layout_next(ctx); if (~opt & MU_OPT_NOFRAME) { ctx->draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG); } push_container(ctx, cnt); push_container_body(ctx, cnt, opt); } void mu_begin_panel(mu_Context *ctx, mu_Container *cnt) { mu_begin_panel_ex(ctx, cnt, 0); } void mu_end_panel(mu_Context *ctx) { mu_pop_clip_rect(ctx); pop_container(ctx); }