ref: dd374fc9c9980c6440845e4f4df7792ec4b4aaf0
dir: /microui.c/
#include <u.h>
#include <libc.h>
#include <draw.h>
#include "microui.h"
#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 mu_Style default_style = {
.font = nil,
.size = { 68, 10 },
.padding = 6,
.spacing = 4,
.indent = 24,
.title_height = 26,
.scrollbar_size = 12,
.thumb_size = 8,
.colors = {nil},
};
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},
};
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 = min(r1.x + r1.w, r2.x + r2.w);
int y2 = 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, 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_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 */
mu_draw_box(ctx, expand_rect(rect, 1), ctx->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(mu_Context *ctx)
{
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 (default_style.colors[0] == nil) {
for (i = 0; i < MU_COLOR_MAX; i++) {
default_style.colors[i] = mu_color(
defaultcolors[i][0],
defaultcolors[i][1],
defaultcolors[i][2],
defaultcolors[i][3]
);
}
}
memset(ctx, 0, sizeof(*ctx));
memmove(&ctx->_style, &default_style, sizeof(default_style));
ctx->style = &ctx->_style;
}
void
mu_begin(mu_Context *ctx)
{
ctx->cmdsnum = ctx->rootnum = 0;
ctx->strnum = 0;
ctx->scroll_target = nil;
ctx->last_hover_root = ctx->hover_root;
ctx->hover_root = nil;
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 */
assert(ctx->cntnum == 0);
assert(ctx->clipnum == 0);
assert(ctx->idsnum == 0);
assert(ctx->layoutsnum == 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->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->scroll_delta = ZP;
ctx->last_mouse_pos = ctx->mouse_pos;
/* sort root containers by zindex */
n = ctx->rootnum;
qsort(ctx->root, n, sizeof(*ctx->root), compare_zindex);
/* set root container jump commands */
for (i = 0; i < n; i++) {
mu_Container *cnt = 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)
ctx->cmds[0].jump.dst = cnt->head + 1;
else
ctx->cmds[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)
ctx->cmds[cnt->tail].jump.dst = ctx->cmdsnum;
}
}
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->idsnum;
mu_Id res = (idx > 0) ? ctx->ids[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)
{
buffer_grow(&ctx->ids, sizeof(*ctx->ids), &ctx->idsmax, ctx->idsnum);
ctx->ids[ctx->idsnum++] = mu_get_id(ctx, data, size);
}
void
mu_pop_id(mu_Context *ctx)
{
ctx->idsnum--;
}
void
mu_push_clip_rect(mu_Context *ctx, mu_Rect rect)
{
mu_Rect last = mu_get_clip_rect(ctx);
buffer_grow(&ctx->clip, sizeof(*ctx->clip), &ctx->clipmax, ctx->clipnum);
ctx->clip[ctx->clipnum++] = clip_rect(rect, last);
}
void
mu_pop_clip_rect(mu_Context *ctx)
{
ctx->clipnum--;
}
mu_Rect
mu_get_clip_rect(mu_Context *ctx)
{
assert(ctx->clipnum > 0);
return ctx->clip[ctx->clipnum - 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, 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(&ctx->layouts, sizeof(*ctx->layouts), &ctx->layoutsmax, ctx->layoutsnum);
ctx->layouts[ctx->layoutsnum++] = layout;
mu_layout_row(ctx, 1, &width, 0);
}
static mu_Layout *
get_layout(mu_Context *ctx)
{
return &ctx->layouts[ctx->layoutsnum - 1];
}
static void
push_container(mu_Context *ctx, mu_Container *cnt)
{
buffer_grow(&ctx->cnt, sizeof(*ctx->cnt), &ctx->cntmax, ctx->cntnum);
ctx->cnt[ctx->cntnum++] = cnt;
mu_push_id(ctx, &cnt, sizeof(mu_Container*));
}
static void
pop_container(mu_Context *ctx)
{
mu_Container *cnt = mu_get_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;
ctx->cntnum--;
ctx->layoutsnum--;
mu_pop_id(ctx);
}
mu_Container *
mu_get_container(mu_Context *ctx)
{
assert(ctx->cntnum > 0);
return ctx->cnt[ctx->cntnum - 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 = Pt(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->text_input);
int size = strlen(text) + 1;
assert(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)
{
mu_Command *cmd;
buffer_grow(&ctx->cmds, sizeof(mu_Command), &ctx->cmdsmax, ctx->cmdsnum);
cmd = &ctx->cmds[ctx->cmdsnum++];
memset(cmd, 0, sizeof(*cmd));
cmd->type = type;
return cmd;
}
static int
push_jump(mu_Context *ctx, int dst)
{
mu_Command *cmd;
cmd = mu_push_command(ctx, MU_COMMAND_JUMP);
cmd->jump.dst = dst;
return ctx->cmdsnum-1;
}
void
mu_set_clip(mu_Context *ctx, mu_Rect rect)
{
mu_Command *cmd;
cmd = mu_push_command(ctx, MU_COMMAND_CLIP);
cmd->clip.rect = rect;
}
void
mu_draw_rect(mu_Context *ctx, mu_Rect rect, Image *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);
cmd->rect.rect = rect;
cmd->rect.color = color;
}
}
void
mu_draw_box(mu_Context *ctx, mu_Rect rect, Image *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, 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(ctx, rect);
if (clipped == MU_CLIP_ALL )
return;
if (clipped == MU_CLIP_PART)
mu_set_clip(ctx, mu_get_clip_rect(ctx));
while (ctx->strmax <= ctx->strnum+len+1) {
ctx->strmax = max(ctx->strmax, ctx->strnum+len+1) * 2;
if ((ctx->str = realloc(ctx->str, ctx->strmax)) == nil)
sysfatal("not enough memory for %d chars", ctx->strmax);
}
cmd = mu_push_command(ctx, MU_COMMAND_TEXT);
cmd->text.s = ctx->strnum;
memmove(ctx->str+ctx->strnum, s, len);
ctx->strnum += len;
ctx->str[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(ctx, unclipped_rect);
}
void
mu_draw_icon(mu_Context *ctx, 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(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);
cmd->icon.id = id;
cmd->icon.rect = rect;
/* reset clipping if it was set */
if (clipped) { mu_set_clip(ctx, unclipped_rect); }
}
/*============================================================================
** layout
**============================================================================*/
enum
{
RELATIVE = 1,
ABSOLUTE,
};
void
mu_layout_begin_column(mu_Context *ctx)
{
push_layout(ctx, mu_layout_next(ctx), ZP);
}
void
mu_layout_end_column(mu_Context *ctx)
{
mu_Layout *a, *b;
b = get_layout(ctx);
ctx->layoutsnum--;
/* inherit position/next_row/max from child layout if they are greater */
a = get_layout(ctx);
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(mu_Context *ctx, int items, const int *widths, int height)
{
mu_Layout *layout = get_layout(ctx);
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(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) {
ctx->last_rect = res;
return res;
}
} else {
/* handle next row */
if (layout->row_index == layout->items)
mu_layout_row(ctx, 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 = 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 = 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 = max(layout->max.x, res.x + res.w);
layout->max.y = max(layout->max.y, res.y + res.h);
ctx->last_rect = res;
return res;
}
/*============================================================================
** controls
**============================================================================*/
static int
in_hover_root(mu_Context *ctx)
{
int i = ctx->cntnum;
while (i--) {
if (ctx->cnt[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->cnt[i]->head >= 0)
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;
draw_frame(ctx, rect, colorid);
}
void
mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, int colorid, int opt)
{
Point pos;
Font *font = ctx->style->font;
int tw = text_width(font, str, -1);
mu_push_clip_rect(ctx, 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 - 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;
Font *font = ctx->style->font;
Image *color = ctx->style->colors[MU_COLOR_TEXT];
mu_layout_begin_column(ctx);
mu_layout_row(ctx, 1, &width, font->height);
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 += 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(ctx, font, start, end - start, Pt(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);
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);
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 = 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;
}
if (ctx->key_pressed & MU_KEY_NACK && len > 0) {
buf[0] = '\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) {
Image *color = ctx->style->colors[MU_COLOR_TEXT];
Font *font = ctx->style->font;
int textw = text_width(font, buf, -1);
int texth = font->height;
int ofx = r.w - ctx->style->padding - textw - 1;
int textx = r.x + 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, Pt(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, float *value, mu_Rect r, mu_Id id)
{
if (((ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->key_down & MU_KEY_SHIFT) || ctx->mouse_pressed == MU_MOUSE_RIGHT) && ctx->hover == id) {
ctx->number_editing = id;
sprint(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, nil);
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, float *value, float low, float high, float step, const char *fmt, int opt)
{
char buf[MU_MAX_FMT + 1];
mu_Rect thumb;
int w, res = 0;
float 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) {
if (ctx->mouse_down == MU_MOUSE_LEFT)
v = low + ((float) (ctx->mouse_pos.x - base.x) / base.w) * (high - low);
} else if (ctx->hover == id) {
if ((ctx->key_pressed & (MU_KEY_LEFT | MU_KEY_RIGHT)) == MU_KEY_LEFT) {
v -= step ? step : 1;
if (v < low) v = low;
} else if ((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 + 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(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 */
sprint(buf, fmt, v);
mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt);
return res;
}
int
mu_slider(mu_Context *ctx, float *value, float low, float high)
{
return mu_slider_ex(ctx, value, low, high, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER);
}
int
mu_number_ex(mu_Context *ctx, float *value, float 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);
float 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 */
sprint(buf, fmt, *value);
mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt);
return res;
}
int
mu_number(mu_Context *ctx, float *value, float step)
{
return mu_number_ex(ctx, value, step, MU_SLIDER_FMT, 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)
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));
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 = clamp(cnt->scroll.y, 0, maxscroll); \
\
/* draw base and thumb */ \
draw_frame(ctx, base, MU_COLOR_SCROLLBASE); \
thumb = base; \
thumb.h = max(ctx->style->thumb_size, base.h * b->h / cs.y); \
thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll; \
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;
Point 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_container(ctx, cnt);
/* push container to roots list and push head command */
buffer_grow(&ctx->root, sizeof(*ctx->root), &ctx->rootmax, ctx->rootnum);
ctx->root[ctx->rootnum++] = cnt;
cnt->head = push_jump(ctx, -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, ctx->mouse_pos) && (!ctx->hover_root || cnt->zindex > ctx->hover_root->zindex))
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(&ctx->clip, sizeof(*ctx->clip), &ctx->clipmax, ctx->clipnum);
ctx->clip[ctx->clipnum++] = 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, -1);
ctx->cmds[cnt->head].jump.dst = ctx->cmdsnum;
/* 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, body, titlerect;
if (!cnt->inited)
mu_init_window(ctx, cnt, opt);
if (!cnt->open)
return 0;
begin_root_container(ctx, cnt);
rect = cnt->rect;
body = rect;
/* draw frame */
if (~opt & MU_OPT_NOFRAME)
draw_frame(ctx, rect, MU_COLOR_WINDOWBG);
/* do title bar */
titlerect = rect;
titlerect.h = ctx->style->title_height;
if (~opt & MU_OPT_NOTITLE) {
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, 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 += titlerect.h;
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);
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->scrollbar_size;
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 = max(96, cnt->rect.w + ctx->mouse_delta.x);
cnt->rect.h = 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->last_hover_root != cnt)
cnt->open = 0;
mu_push_clip_rect(ctx, cnt->body);
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)
{
return mu_begin_window_ex(
ctx,
cnt,
"",
MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE | MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED
);
}
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)
draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG);
push_container(ctx, cnt);
push_container_body(ctx, cnt, cnt->rect, opt);
mu_push_clip_rect(ctx, cnt->body);
}
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);
}
int
mu_render(mu_Context *ctx)
{
mu_Command *cmd;
mu_Rect r, iconr;
if (memcmp(&ctx->screen, &screen->r, sizeof(ctx->screen)) != 0)
ctx->screen = screen->r;
else if (ctx->oldcmdsnum == ctx->cmdsnum && memcmp(ctx->oldcmds, ctx->cmds, ctx->cmdsnum*sizeof(mu_Command)) == 0)
return 0;
if (ctx->oldcmdsmax != ctx->cmdsmax && (ctx->oldcmds = realloc(ctx->oldcmds, ctx->cmdsmax*sizeof(mu_Command))) == nil)
sysfatal("couldn't allocate memory for old cmds");
ctx->oldcmdsmax = ctx->cmdsmax;
ctx->oldcmdsnum = ctx->cmdsnum;
memmove(ctx->oldcmds, ctx->cmds, ctx->cmdsnum*sizeof(mu_Command));
if (ctx->style->colors[MU_COLOR_BG] != nil)
draw(screen, screen->r, ctx->style->colors[MU_COLOR_BG], nil, ZP);
for (cmd = ctx->cmds; cmd < ctx->cmds + 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, ctx->style->font, 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 = &ctx->cmds[cmd->jump.dst];
continue;
}
cmd++;
}
return 1;
}