shithub: neindaw

ref: 2f144ccad768088ec811f0945eb8611fbc1e0218
dir: /microui/microui.c/

View raw version
#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;
}