You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1208 lines
37 KiB
1208 lines
37 KiB
/* |
|
** Copyright (c) 2024 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 <stdlib.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++; /* incremented after incase `val` uses this value */ \ |
|
} while (0) |
|
|
|
#define pop(stk) do { \ |
|
expect((stk).idx > 0); \ |
|
(stk).idx--; \ |
|
} while (0) |
|
|
|
|
|
static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 }; |
|
|
|
static mu_Style default_style = { |
|
/* font | size | padding | spacing | indent */ |
|
NULL, { 68, 10 }, 5, 4, 24, |
|
/* title_height | scrollbar_size | thumb_size */ |
|
24, 12, 8, |
|
{ |
|
{ 230, 230, 230, 255 }, /* MU_COLOR_TEXT */ |
|
{ 25, 25, 25, 255 }, /* MU_COLOR_BORDER */ |
|
{ 50, 50, 50, 255 }, /* MU_COLOR_WINDOWBG */ |
|
{ 25, 25, 25, 255 }, /* MU_COLOR_TITLEBG */ |
|
{ 240, 240, 240, 255 }, /* MU_COLOR_TITLETEXT */ |
|
{ 0, 0, 0, 0 }, /* MU_COLOR_PANELBG */ |
|
{ 75, 75, 75, 255 }, /* MU_COLOR_BUTTON */ |
|
{ 95, 95, 95, 255 }, /* MU_COLOR_BUTTONHOVER */ |
|
{ 115, 115, 115, 255 }, /* MU_COLOR_BUTTONFOCUS */ |
|
{ 30, 30, 30, 255 }, /* MU_COLOR_BASE */ |
|
{ 35, 35, 35, 255 }, /* MU_COLOR_BASEHOVER */ |
|
{ 40, 40, 40, 255 }, /* MU_COLOR_BASEFOCUS */ |
|
{ 43, 43, 43, 255 }, /* MU_COLOR_SCROLLBASE */ |
|
{ 30, 30, 30, 255 } /* MU_COLOR_SCROLLTHUMB */ |
|
} |
|
}; |
|
|
|
|
|
mu_Vec2 mu_vec2(int x, int y) { |
|
mu_Vec2 res; |
|
res.x = x; res.y = y; |
|
return res; |
|
} |
|
|
|
|
|
mu_Rect mu_rect(int x, int y, int w, int h) { |
|
mu_Rect res; |
|
res.x = x; res.y = y; res.w = w; res.h = h; |
|
return res; |
|
} |
|
|
|
|
|
mu_Color mu_color(int r, int g, int b, int a) { |
|
mu_Color res; |
|
res.r = r; res.g = g; res.b = b; res.a = a; |
|
return res; |
|
} |
|
|
|
|
|
static mu_Rect expand_rect(mu_Rect rect, int n) { |
|
return mu_rect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2); |
|
} |
|
|
|
|
|
static mu_Rect intersect_rects(mu_Rect r1, mu_Rect r2) { |
|
int x1 = mu_max(r1.x, r2.x); |
|
int y1 = mu_max(r1.y, r2.y); |
|
int x2 = mu_min(r1.x + r1.w, r2.x + r2.w); |
|
int y2 = mu_min(r1.y + r1.h, r2.y + r2.h); |
|
if (x2 < x1) { x2 = x1; } |
|
if (y2 < y1) { y2 = y1; } |
|
return mu_rect(x1, y1, x2 - x1, y2 - y1); |
|
} |
|
|
|
|
|
static int rect_overlaps_vec2(mu_Rect r, mu_Vec2 p) { |
|
return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h; |
|
} |
|
|
|
|
|
static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid) { |
|
mu_draw_rect(ctx, rect, ctx->style->colors[colorid]); |
|
if (colorid == MU_COLOR_SCROLLBASE || |
|
colorid == MU_COLOR_SCROLLTHUMB || |
|
colorid == MU_COLOR_TITLEBG) { return; } |
|
/* draw border */ |
|
if (ctx->style->colors[MU_COLOR_BORDER].a) { |
|
mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_BORDER]); |
|
} |
|
} |
|
|
|
|
|
void mu_init(mu_Context *ctx) { |
|
memset(ctx, 0, sizeof(*ctx)); |
|
ctx->draw_frame = draw_frame; |
|
ctx->_style = default_style; |
|
ctx->style = &ctx->_style; |
|
} |
|
|
|
|
|
void mu_begin(mu_Context *ctx) { |
|
expect(ctx->text_width && ctx->text_height); |
|
ctx->command_list.idx = 0; |
|
ctx->root_list.idx = 0; |
|
ctx->scroll_target = NULL; |
|
ctx->hover_root = ctx->next_hover_root; |
|
ctx->next_hover_root = NULL; |
|
ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x; |
|
ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y; |
|
ctx->frame++; |
|
} |
|
|
|
|
|
static int compare_zindex(const void *a, const void *b) { |
|
return (*(mu_Container**) a)->zindex - (*(mu_Container**) b)->zindex; |
|
} |
|
|
|
|
|
void mu_end(mu_Context *ctx) { |
|
int i, n; |
|
/* check stacks */ |
|
expect(ctx->container_stack.idx == 0); |
|
expect(ctx->clip_stack.idx == 0); |
|
expect(ctx->id_stack.idx == 0); |
|
expect(ctx->layout_stack.idx == 0); |
|
|
|
/* handle scroll input */ |
|
if (ctx->scroll_target) { |
|
ctx->scroll_target->scroll.x += ctx->scroll_delta.x; |
|
ctx->scroll_target->scroll.y += ctx->scroll_delta.y; |
|
} |
|
|
|
/* unset focus if focus id was not touched this frame */ |
|
if (!ctx->updated_focus) { ctx->focus = 0; } |
|
ctx->updated_focus = 0; |
|
|
|
/* bring hover root to front if mouse was pressed */ |
|
if (ctx->mouse_pressed && ctx->next_hover_root && |
|
ctx->next_hover_root->zindex < ctx->last_zindex && |
|
ctx->next_hover_root->zindex >= 0 |
|
) { |
|
mu_bring_to_front(ctx, ctx->next_hover_root); |
|
} |
|
|
|
/* reset input state */ |
|
ctx->key_pressed = 0; |
|
ctx->input_text[0] = '\0'; |
|
ctx->mouse_pressed = 0; |
|
ctx->scroll_delta = mu_vec2(0, 0); |
|
ctx->last_mouse_pos = ctx->mouse_pos; |
|
|
|
/* sort root containers by zindex */ |
|
n = ctx->root_list.idx; |
|
qsort(ctx->root_list.items, n, sizeof(mu_Container*), compare_zindex); |
|
|
|
/* set root container jump commands */ |
|
for (i = 0; i < n; i++) { |
|
mu_Container *cnt = ctx->root_list.items[i]; |
|
/* if this is the first container then make the first command jump to it. |
|
** otherwise set the previous container's tail to jump to this one */ |
|
if (i == 0) { |
|
mu_Command *cmd = (mu_Command*) ctx->command_list.items; |
|
cmd->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand); |
|
} else { |
|
mu_Container *prev = ctx->root_list.items[i - 1]; |
|
prev->tail->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand); |
|
} |
|
/* make the last container's tail jump to the end of command list */ |
|
if (i == n - 1) { |
|
cnt->tail->jump.dst = ctx->command_list.items + ctx->command_list.idx; |
|
} |
|
} |
|
} |
|
|
|
|
|
void mu_set_focus(mu_Context *ctx, mu_Id id) { |
|
ctx->focus = id; |
|
ctx->updated_focus = 1; |
|
} |
|
|
|
|
|
/* 32bit fnv-1a hash */ |
|
#define HASH_INITIAL 2166136261 |
|
|
|
static void hash(mu_Id *hash, const void *data, int size) { |
|
const unsigned char *p = data; |
|
while (size--) { |
|
*hash = (*hash ^ *p++) * 16777619; |
|
} |
|
} |
|
|
|
|
|
mu_Id mu_get_id(mu_Context *ctx, const void *data, int size) { |
|
int idx = ctx->id_stack.idx; |
|
mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL; |
|
hash(&res, data, size); |
|
ctx->last_id = res; |
|
return res; |
|
} |
|
|
|
|
|
void mu_push_id(mu_Context *ctx, const void *data, int size) { |
|
push(ctx->id_stack, mu_get_id(ctx, data, size)); |
|
} |
|
|
|
|
|
void mu_pop_id(mu_Context *ctx) { |
|
pop(ctx->id_stack); |
|
} |
|
|
|
|
|
void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect) { |
|
mu_Rect last = mu_get_clip_rect(ctx); |
|
push(ctx->clip_stack, intersect_rects(rect, last)); |
|
} |
|
|
|
|
|
void mu_pop_clip_rect(mu_Context *ctx) { |
|
pop(ctx->clip_stack); |
|
} |
|
|
|
|
|
mu_Rect mu_get_clip_rect(mu_Context *ctx) { |
|
expect(ctx->clip_stack.idx > 0); |
|
return ctx->clip_stack.items[ctx->clip_stack.idx - 1]; |
|
} |
|
|
|
|
|
int mu_check_clip(mu_Context *ctx, mu_Rect r) { |
|
mu_Rect cr = mu_get_clip_rect(ctx); |
|
if (r.x > cr.x + cr.w || r.x + r.w < cr.x || |
|
r.y > cr.y + cr.h || r.y + r.h < cr.y ) { return MU_CLIP_ALL; } |
|
if (r.x >= cr.x && r.x + r.w <= cr.x + cr.w && |
|
r.y >= cr.y && r.y + r.h <= cr.y + cr.h ) { return 0; } |
|
return MU_CLIP_PART; |
|
} |
|
|
|
|
|
static void push_layout(mu_Context *ctx, mu_Rect body, mu_Vec2 scroll) { |
|
mu_Layout layout; |
|
int width = 0; |
|
memset(&layout, 0, sizeof(layout)); |
|
layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h); |
|
layout.max = mu_vec2(-0x1000000, -0x1000000); |
|
push(ctx->layout_stack, layout); |
|
mu_layout_row(ctx, 1, &width, 0); |
|
} |
|
|
|
|
|
static mu_Layout* get_layout(mu_Context *ctx) { |
|
return &ctx->layout_stack.items[ctx->layout_stack.idx - 1]; |
|
} |
|
|
|
|
|
static void pop_container(mu_Context *ctx) { |
|
mu_Container *cnt = mu_get_current_container(ctx); |
|
mu_Layout *layout = get_layout(ctx); |
|
cnt->content_size.x = layout->max.x - layout->body.x; |
|
cnt->content_size.y = layout->max.y - layout->body.y; |
|
/* pop container, layout and id */ |
|
pop(ctx->container_stack); |
|
pop(ctx->layout_stack); |
|
mu_pop_id(ctx); |
|
} |
|
|
|
|
|
mu_Container* mu_get_current_container(mu_Context *ctx) { |
|
expect(ctx->container_stack.idx > 0); |
|
return ctx->container_stack.items[ ctx->container_stack.idx - 1 ]; |
|
} |
|
|
|
|
|
static mu_Container* get_container(mu_Context *ctx, mu_Id id, int opt) { |
|
mu_Container *cnt; |
|
/* try to get existing container from pool */ |
|
int idx = mu_pool_get(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id); |
|
if (idx >= 0) { |
|
if (ctx->containers[idx].open || ~opt & MU_OPT_CLOSED) { |
|
mu_pool_update(ctx, ctx->container_pool, idx); |
|
} |
|
return &ctx->containers[idx]; |
|
} |
|
if (opt & MU_OPT_CLOSED) { return NULL; } |
|
/* container not found in pool: init new container */ |
|
idx = mu_pool_init(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id); |
|
cnt = &ctx->containers[idx]; |
|
memset(cnt, 0, sizeof(*cnt)); |
|
cnt->open = 1; |
|
mu_bring_to_front(ctx, cnt); |
|
return cnt; |
|
} |
|
|
|
|
|
mu_Container* mu_get_container(mu_Context *ctx, const char *name) { |
|
mu_Id id = mu_get_id(ctx, name, strlen(name)); |
|
return get_container(ctx, id, 0); |
|
} |
|
|
|
|
|
void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt) { |
|
cnt->zindex = ++ctx->last_zindex; |
|
} |
|
|
|
|
|
/*============================================================================ |
|
** pool |
|
**============================================================================*/ |
|
|
|
int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) { |
|
int i, n = -1, f = ctx->frame; |
|
for (i = 0; i < len; i++) { |
|
if (items[i].last_update < f) { |
|
f = items[i].last_update; |
|
n = i; |
|
} |
|
} |
|
expect(n > -1); |
|
items[n].id = id; |
|
mu_pool_update(ctx, items, n); |
|
return n; |
|
} |
|
|
|
|
|
int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) { |
|
int i; |
|
unused(ctx); |
|
for (i = 0; i < len; i++) { |
|
if (items[i].id == id) { return i; } |
|
} |
|
return -1; |
|
} |
|
|
|
|
|
void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx) { |
|
items[idx].last_update = ctx->frame; |
|
} |
|
|
|
|
|
/*============================================================================ |
|
** input handlers |
|
**============================================================================*/ |
|
|
|
void mu_input_mousemove(mu_Context *ctx, int x, int y) { |
|
ctx->mouse_pos = mu_vec2(x, y); |
|
} |
|
|
|
|
|
void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn) { |
|
mu_input_mousemove(ctx, x, y); |
|
ctx->mouse_down |= btn; |
|
ctx->mouse_pressed |= btn; |
|
} |
|
|
|
|
|
void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn) { |
|
mu_input_mousemove(ctx, x, y); |
|
ctx->mouse_down &= ~btn; |
|
} |
|
|
|
|
|
void mu_input_scroll(mu_Context *ctx, int x, int y) { |
|
ctx->scroll_delta.x += x; |
|
ctx->scroll_delta.y += y; |
|
} |
|
|
|
|
|
void mu_input_keydown(mu_Context *ctx, int key) { |
|
ctx->key_pressed |= key; |
|
ctx->key_down |= key; |
|
} |
|
|
|
|
|
void mu_input_keyup(mu_Context *ctx, int key) { |
|
ctx->key_down &= ~key; |
|
} |
|
|
|
|
|
void mu_input_text(mu_Context *ctx, const char *text) { |
|
int len = strlen(ctx->input_text); |
|
int size = strlen(text) + 1; |
|
expect(len + size <= (int) sizeof(ctx->input_text)); |
|
memcpy(ctx->input_text + len, text, size); |
|
} |
|
|
|
|
|
/*============================================================================ |
|
** commandlist |
|
**============================================================================*/ |
|
|
|
mu_Command* mu_push_command(mu_Context *ctx, int type, int size) { |
|
mu_Command *cmd = (mu_Command*) (ctx->command_list.items + ctx->command_list.idx); |
|
expect(ctx->command_list.idx + size < MU_COMMANDLIST_SIZE); |
|
cmd->base.type = type; |
|
cmd->base.size = size; |
|
ctx->command_list.idx += size; |
|
return cmd; |
|
} |
|
|
|
|
|
int mu_next_command(mu_Context *ctx, mu_Command **cmd) { |
|
if (*cmd) { |
|
*cmd = (mu_Command*) (((char*) *cmd) + (*cmd)->base.size); |
|
} else { |
|
*cmd = (mu_Command*) ctx->command_list.items; |
|
} |
|
while ((char*) *cmd != ctx->command_list.items + ctx->command_list.idx) { |
|
if ((*cmd)->type != MU_COMMAND_JUMP) { return 1; } |
|
*cmd = (*cmd)->jump.dst; |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
static mu_Command* push_jump(mu_Context *ctx, mu_Command *dst) { |
|
mu_Command *cmd; |
|
cmd = mu_push_command(ctx, MU_COMMAND_JUMP, sizeof(mu_JumpCommand)); |
|
cmd->jump.dst = dst; |
|
return cmd; |
|
} |
|
|
|
|
|
void mu_set_clip(mu_Context *ctx, mu_Rect rect) { |
|
mu_Command *cmd; |
|
cmd = mu_push_command(ctx, MU_COMMAND_CLIP, sizeof(mu_ClipCommand)); |
|
cmd->clip.rect = rect; |
|
} |
|
|
|
|
|
void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color) { |
|
mu_Command *cmd; |
|
rect = intersect_rects(rect, mu_get_clip_rect(ctx)); |
|
if (rect.w > 0 && rect.h > 0) { |
|
cmd = mu_push_command(ctx, MU_COMMAND_RECT, sizeof(mu_RectCommand)); |
|
cmd->rect.rect = rect; |
|
cmd->rect.color = color; |
|
} |
|
} |
|
|
|
|
|
void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color) { |
|
mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color); |
|
mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color); |
|
mu_draw_rect(ctx, mu_rect(rect.x, rect.y, 1, rect.h), color); |
|
mu_draw_rect(ctx, mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color); |
|
} |
|
|
|
|
|
void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len, |
|
mu_Vec2 pos, mu_Color color) |
|
{ |
|
mu_Command *cmd; |
|
mu_Rect rect = mu_rect( |
|
pos.x, pos.y, ctx->text_width(font, str, len), ctx->text_height(font)); |
|
int clipped = mu_check_clip(ctx, rect); |
|
if (clipped == MU_CLIP_ALL ) { return; } |
|
if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); } |
|
/* add command */ |
|
if (len < 0) { len = strlen(str); } |
|
cmd = mu_push_command(ctx, MU_COMMAND_TEXT, sizeof(mu_TextCommand) + len); |
|
memcpy(cmd->text.str, str, len); |
|
cmd->text.str[len] = '\0'; |
|
cmd->text.pos = pos; |
|
cmd->text.color = color; |
|
cmd->text.font = font; |
|
/* reset clipping if it was set */ |
|
if (clipped) { mu_set_clip(ctx, unclipped_rect); } |
|
} |
|
|
|
|
|
void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color) { |
|
mu_Command *cmd; |
|
/* do clip command if the rect isn't fully contained within the cliprect */ |
|
int clipped = mu_check_clip(ctx, rect); |
|
if (clipped == MU_CLIP_ALL ) { return; } |
|
if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); } |
|
/* do icon command */ |
|
cmd = mu_push_command(ctx, MU_COMMAND_ICON, sizeof(mu_IconCommand)); |
|
cmd->icon.id = id; |
|
cmd->icon.rect = rect; |
|
cmd->icon.color = color; |
|
/* reset clipping if it was set */ |
|
if (clipped) { mu_set_clip(ctx, unclipped_rect); } |
|
} |
|
|
|
|
|
/*============================================================================ |
|
** layout |
|
**============================================================================*/ |
|
|
|
enum { RELATIVE = 1, ABSOLUTE = 2 }; |
|
|
|
|
|
void mu_layout_begin_column(mu_Context *ctx) { |
|
push_layout(ctx, mu_layout_next(ctx), mu_vec2(0, 0)); |
|
} |
|
|
|
|
|
void mu_layout_end_column(mu_Context *ctx) { |
|
mu_Layout *a, *b; |
|
b = get_layout(ctx); |
|
pop(ctx->layout_stack); |
|
/* inherit position/next_row/max from child layout if they are greater */ |
|
a = get_layout(ctx); |
|
a->position.x = mu_max(a->position.x, b->position.x + b->body.x - a->body.x); |
|
a->next_row = mu_max(a->next_row, b->next_row + b->body.y - a->body.y); |
|
a->max.x = mu_max(a->max.x, b->max.x); |
|
a->max.y = mu_max(a->max.y, b->max.y); |
|
} |
|
|
|
|
|
void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height) { |
|
mu_Layout *layout = get_layout(ctx); |
|
if (widths) { |
|
expect(items <= MU_MAX_WIDTHS); |
|
memcpy(layout->widths, widths, items * sizeof(widths[0])); |
|
} |
|
layout->items = items; |
|
layout->position = mu_vec2(layout->indent, layout->next_row); |
|
layout->size.y = height; |
|
layout->item_index = 0; |
|
} |
|
|
|
|
|
void mu_layout_width(mu_Context *ctx, int width) { |
|
get_layout(ctx)->size.x = width; |
|
} |
|
|
|
|
|
void mu_layout_height(mu_Context *ctx, int height) { |
|
get_layout(ctx)->size.y = height; |
|
} |
|
|
|
|
|
void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative) { |
|
mu_Layout *layout = get_layout(ctx); |
|
layout->next = r; |
|
layout->next_type = relative ? RELATIVE : ABSOLUTE; |
|
} |
|
|
|
|
|
mu_Rect mu_layout_next(mu_Context *ctx) { |
|
mu_Layout *layout = get_layout(ctx); |
|
mu_Style *style = ctx->style; |
|
mu_Rect res; |
|
|
|
if (layout->next_type) { |
|
/* handle rect set by `mu_layout_set_next` */ |
|
int type = layout->next_type; |
|
layout->next_type = 0; |
|
res = layout->next; |
|
if (type == ABSOLUTE) { return (ctx->last_rect = res); } |
|
|
|
} else { |
|
/* handle next row */ |
|
if (layout->item_index == layout->items) { |
|
mu_layout_row(ctx, layout->items, NULL, layout->size.y); |
|
} |
|
|
|
/* position */ |
|
res.x = layout->position.x; |
|
res.y = layout->position.y; |
|
|
|
/* size */ |
|
res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x; |
|
res.h = layout->size.y; |
|
if (res.w == 0) { res.w = style->size.x + style->padding * 2; } |
|
if (res.h == 0) { res.h = style->size.y + style->padding * 2; } |
|
if (res.w < 0) { res.w += layout->body.w - res.x + 1; } |
|
if (res.h < 0) { res.h += layout->body.h - res.y + 1; } |
|
|
|
layout->item_index++; |
|
} |
|
|
|
/* update position */ |
|
layout->position.x += res.w + style->spacing; |
|
layout->next_row = mu_max(layout->next_row, res.y + res.h + style->spacing); |
|
|
|
/* apply body offset */ |
|
res.x += layout->body.x; |
|
res.y += layout->body.y; |
|
|
|
/* update max position */ |
|
layout->max.x = mu_max(layout->max.x, res.x + res.w); |
|
layout->max.y = mu_max(layout->max.y, res.y + res.h); |
|
|
|
return (ctx->last_rect = res); |
|
} |
|
|
|
|
|
/*============================================================================ |
|
** controls |
|
**============================================================================*/ |
|
|
|
static int in_hover_root(mu_Context *ctx) { |
|
int i = ctx->container_stack.idx; |
|
while (i--) { |
|
if (ctx->container_stack.items[i] == ctx->hover_root) { return 1; } |
|
/* only root containers have their `head` field set; stop searching if we've |
|
** reached the current root container */ |
|
if (ctx->container_stack.items[i]->head) { break; } |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect, |
|
int colorid, int opt) |
|
{ |
|
if (opt & MU_OPT_NOFRAME) { return; } |
|
colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0; |
|
ctx->draw_frame(ctx, rect, colorid); |
|
} |
|
|
|
|
|
void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, |
|
int colorid, int opt) |
|
{ |
|
mu_Vec2 pos; |
|
mu_Font font = ctx->style->font; |
|
int tw = ctx->text_width(font, str, -1); |
|
mu_push_clip_rect(ctx, rect); |
|
pos.y = rect.y + (rect.h - ctx->text_height(font)) / 2; |
|
if (opt & MU_OPT_ALIGNCENTER) { |
|
pos.x = rect.x + (rect.w - tw) / 2; |
|
} else if (opt & MU_OPT_ALIGNRIGHT) { |
|
pos.x = rect.x + rect.w - tw - ctx->style->padding; |
|
} else { |
|
pos.x = rect.x + ctx->style->padding; |
|
} |
|
mu_draw_text(ctx, font, str, -1, pos, ctx->style->colors[colorid]); |
|
mu_pop_clip_rect(ctx); |
|
} |
|
|
|
|
|
int mu_mouse_over(mu_Context *ctx, mu_Rect rect) { |
|
return rect_overlaps_vec2(rect, ctx->mouse_pos) && |
|
rect_overlaps_vec2(mu_get_clip_rect(ctx), ctx->mouse_pos) && |
|
in_hover_root(ctx); |
|
} |
|
|
|
|
|
void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) { |
|
int mouseover = mu_mouse_over(ctx, rect); |
|
|
|
if (ctx->focus == id) { ctx->updated_focus = 1; } |
|
if (opt & MU_OPT_NOINTERACT) { return; } |
|
if (mouseover && !ctx->mouse_down) { ctx->hover = id; } |
|
|
|
if (ctx->focus == id) { |
|
if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); } |
|
if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); } |
|
} |
|
|
|
if (ctx->hover == id) { |
|
if (ctx->mouse_pressed) { |
|
mu_set_focus(ctx, id); |
|
} else if (!mouseover) { |
|
ctx->hover = 0; |
|
} |
|
} |
|
} |
|
|
|
|
|
void mu_text(mu_Context *ctx, const char *text) { |
|
const char *start, *end, *p = text; |
|
int width = -1; |
|
mu_Font font = ctx->style->font; |
|
mu_Color color = ctx->style->colors[MU_COLOR_TEXT]; |
|
mu_layout_begin_column(ctx); |
|
mu_layout_row(ctx, 1, &width, ctx->text_height(font)); |
|
do { |
|
mu_Rect r = mu_layout_next(ctx); |
|
int w = 0; |
|
start = end = p; |
|
do { |
|
const char* word = p; |
|
while (*p && *p != ' ' && *p != '\n') { p++; } |
|
w += ctx->text_width(font, word, p - word); |
|
if (w > r.w && end != start) { break; } |
|
w += ctx->text_width(font, p, 1); |
|
end = p++; |
|
} while (*end && *end != '\n'); |
|
mu_draw_text(ctx, font, start, end - start, mu_vec2(r.x, r.y), color); |
|
p = end + 1; |
|
} while (*end); |
|
mu_layout_end_column(ctx); |
|
} |
|
|
|
|
|
void mu_label(mu_Context *ctx, const char *text) { |
|
mu_draw_control_text(ctx, text, mu_layout_next(ctx), MU_COLOR_TEXT, 0); |
|
} |
|
|
|
|
|
int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) { |
|
int res = 0; |
|
mu_Id id = label ? mu_get_id(ctx, label, strlen(label)) |
|
: mu_get_id(ctx, &icon, sizeof(icon)); |
|
mu_Rect r = mu_layout_next(ctx); |
|
mu_update_control(ctx, id, r, opt); |
|
/* handle click */ |
|
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { |
|
res |= MU_RES_SUBMIT; |
|
} |
|
/* draw */ |
|
mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, opt); |
|
if (label) { mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, opt); } |
|
if (icon) { mu_draw_icon(ctx, icon, r, ctx->style->colors[MU_COLOR_TEXT]); } |
|
return res; |
|
} |
|
|
|
|
|
int mu_checkbox(mu_Context *ctx, const char *label, int *state) { |
|
int res = 0; |
|
mu_Id id = mu_get_id(ctx, &state, sizeof(state)); |
|
mu_Rect r = mu_layout_next(ctx); |
|
mu_Rect box = mu_rect(r.x, r.y, r.h, r.h); |
|
mu_update_control(ctx, id, r, 0); |
|
/* handle click */ |
|
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) { |
|
res |= MU_RES_CHANGE; |
|
*state = !*state; |
|
} |
|
/* draw */ |
|
mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0); |
|
if (*state) { |
|
mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]); |
|
} |
|
r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h); |
|
mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); |
|
return res; |
|
} |
|
|
|
|
|
int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, |
|
int opt) |
|
{ |
|
int res = 0; |
|
mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS); |
|
|
|
if (ctx->focus == id) { |
|
/* handle text input */ |
|
int len = strlen(buf); |
|
int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text)); |
|
if (n > 0) { |
|
memcpy(buf + len, ctx->input_text, n); |
|
len += n; |
|
buf[len] = '\0'; |
|
res |= MU_RES_CHANGE; |
|
} |
|
/* handle backspace */ |
|
if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) { |
|
/* skip utf-8 continuation bytes */ |
|
while ((buf[--len] & 0xc0) == 0x80 && len > 0); |
|
buf[len] = '\0'; |
|
res |= MU_RES_CHANGE; |
|
} |
|
/* handle return */ |
|
if (ctx->key_pressed & MU_KEY_RETURN) { |
|
mu_set_focus(ctx, 0); |
|
res |= MU_RES_SUBMIT; |
|
} |
|
} |
|
|
|
/* draw */ |
|
mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt); |
|
if (ctx->focus == id) { |
|
mu_Color color = ctx->style->colors[MU_COLOR_TEXT]; |
|
mu_Font font = ctx->style->font; |
|
int textw = ctx->text_width(font, buf, -1); |
|
int texth = ctx->text_height(font); |
|
int ofx = r.w - ctx->style->padding - textw - 1; |
|
int textx = r.x + mu_min(ofx, ctx->style->padding); |
|
int texty = r.y + (r.h - texth) / 2; |
|
mu_push_clip_rect(ctx, r); |
|
mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color); |
|
mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color); |
|
mu_pop_clip_rect(ctx); |
|
} else { |
|
mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt); |
|
} |
|
|
|
return res; |
|
} |
|
|
|
|
|
static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id) { |
|
if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->key_down & MU_KEY_SHIFT && |
|
ctx->hover == id |
|
) { |
|
ctx->number_edit = id; |
|
sprintf(ctx->number_edit_buf, MU_REAL_FMT, *value); |
|
} |
|
if (ctx->number_edit == id) { |
|
int res = mu_textbox_raw( |
|
ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0); |
|
if (res & MU_RES_SUBMIT || ctx->focus != id) { |
|
*value = strtod(ctx->number_edit_buf, NULL); |
|
ctx->number_edit = 0; |
|
} else { |
|
return 1; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) { |
|
mu_Id id = mu_get_id(ctx, &buf, sizeof(buf)); |
|
mu_Rect r = mu_layout_next(ctx); |
|
return mu_textbox_raw(ctx, buf, bufsz, id, r, opt); |
|
} |
|
|
|
|
|
int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, |
|
mu_Real step, const char *fmt, int opt) |
|
{ |
|
char buf[MU_MAX_FMT + 1]; |
|
mu_Rect thumb; |
|
int x, w, res = 0; |
|
mu_Real last = *value, v = last; |
|
mu_Id id = mu_get_id(ctx, &value, sizeof(value)); |
|
mu_Rect base = mu_layout_next(ctx); |
|
|
|
/* handle text input mode */ |
|
if (number_textbox(ctx, &v, base, id)) { return res; } |
|
|
|
/* handle normal mode */ |
|
mu_update_control(ctx, id, base, opt); |
|
|
|
/* handle input */ |
|
if (ctx->focus == id && |
|
(ctx->mouse_down | ctx->mouse_pressed) == MU_MOUSE_LEFT) |
|
{ |
|
v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w; |
|
if (step) { v = ((long long)((v + step / 2) / step)) * step; } |
|
} |
|
/* clamp and store value, update res */ |
|
*value = v = mu_clamp(v, low, high); |
|
if (last != v) { res |= MU_RES_CHANGE; } |
|
|
|
/* draw base */ |
|
mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt); |
|
/* draw thumb */ |
|
w = ctx->style->thumb_size; |
|
x = (v - low) * (base.w - w) / (high - low); |
|
thumb = mu_rect(base.x + x, base.y, w, base.h); |
|
mu_draw_control_frame(ctx, id, thumb, MU_COLOR_BUTTON, opt); |
|
/* draw text */ |
|
sprintf(buf, fmt, v); |
|
mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt); |
|
|
|
return res; |
|
} |
|
|
|
|
|
int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, |
|
const char *fmt, int opt) |
|
{ |
|
char buf[MU_MAX_FMT + 1]; |
|
int res = 0; |
|
mu_Id id = mu_get_id(ctx, &value, sizeof(value)); |
|
mu_Rect base = mu_layout_next(ctx); |
|
mu_Real last = *value; |
|
|
|
/* handle text input mode */ |
|
if (number_textbox(ctx, value, base, id)) { return res; } |
|
|
|
/* handle normal mode */ |
|
mu_update_control(ctx, id, base, opt); |
|
|
|
/* handle input */ |
|
if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { |
|
*value += ctx->mouse_delta.x * step; |
|
} |
|
/* set flag if value changed */ |
|
if (*value != last) { res |= MU_RES_CHANGE; } |
|
|
|
/* draw base */ |
|
mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt); |
|
/* draw text */ |
|
sprintf(buf, fmt, *value); |
|
mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt); |
|
|
|
return res; |
|
} |
|
|
|
|
|
static int header(mu_Context *ctx, const char *label, int istreenode, int opt) { |
|
mu_Rect r; |
|
int active, expanded; |
|
mu_Id id = mu_get_id(ctx, label, strlen(label)); |
|
int idx = mu_pool_get(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id); |
|
int width = -1; |
|
mu_layout_row(ctx, 1, &width, 0); |
|
|
|
active = (idx >= 0); |
|
expanded = (opt & MU_OPT_EXPANDED) ? !active : active; |
|
r = mu_layout_next(ctx); |
|
mu_update_control(ctx, id, r, 0); |
|
|
|
/* handle click */ |
|
active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id); |
|
|
|
/* update pool ref */ |
|
if (idx >= 0) { |
|
if (active) { mu_pool_update(ctx, ctx->treenode_pool, idx); } |
|
else { memset(&ctx->treenode_pool[idx], 0, sizeof(mu_PoolItem)); } |
|
} else if (active) { |
|
mu_pool_init(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id); |
|
} |
|
|
|
/* draw */ |
|
if (istreenode) { |
|
if (ctx->hover == id) { ctx->draw_frame(ctx, r, MU_COLOR_BUTTONHOVER); } |
|
} else { |
|
mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, 0); |
|
} |
|
mu_draw_icon( |
|
ctx, expanded ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED, |
|
mu_rect(r.x, r.y, r.h, r.h), ctx->style->colors[MU_COLOR_TEXT]); |
|
r.x += r.h - ctx->style->padding; |
|
r.w -= r.h - ctx->style->padding; |
|
mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0); |
|
|
|
return expanded ? MU_RES_ACTIVE : 0; |
|
} |
|
|
|
|
|
int mu_header_ex(mu_Context *ctx, const char *label, int opt) { |
|
return header(ctx, label, 0, opt); |
|
} |
|
|
|
|
|
int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt) { |
|
int res = header(ctx, label, 1, opt); |
|
if (res & MU_RES_ACTIVE) { |
|
get_layout(ctx)->indent += ctx->style->indent; |
|
push(ctx->id_stack, ctx->last_id); |
|
} |
|
return res; |
|
} |
|
|
|
|
|
void mu_end_treenode(mu_Context *ctx) { |
|
get_layout(ctx)->indent -= ctx->style->indent; |
|
mu_pop_id(ctx); |
|
} |
|
|
|
|
|
#define scrollbar(ctx, cnt, b, cs, x, y, w, h) \ |
|
do { \ |
|
/* only add scrollbar if content size is larger than body */ \ |
|
int maxscroll = cs.y - b->h; \ |
|
\ |
|
if (maxscroll > 0 && b->h > 0) { \ |
|
mu_Rect base, thumb; \ |
|
mu_Id id = mu_get_id(ctx, "!scrollbar" #y, 11); \ |
|
\ |
|
/* get sizing / positioning */ \ |
|
base = *b; \ |
|
base.x = b->x + b->w; \ |
|
base.w = ctx->style->scrollbar_size; \ |
|
\ |
|
/* handle input */ \ |
|
mu_update_control(ctx, id, base, 0); \ |
|
if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) { \ |
|
cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h; \ |
|
} \ |
|
/* clamp scroll to limits */ \ |
|
cnt->scroll.y = mu_clamp(cnt->scroll.y, 0, maxscroll); \ |
|
\ |
|
/* draw base and thumb */ \ |
|
ctx->draw_frame(ctx, base, MU_COLOR_SCROLLBASE); \ |
|
thumb = base; \ |
|
thumb.h = mu_max(ctx->style->thumb_size, base.h * b->h / cs.y); \ |
|
thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll; \ |
|
ctx->draw_frame(ctx, thumb, MU_COLOR_SCROLLTHUMB); \ |
|
\ |
|
/* set this as the scroll_target (will get scrolled on mousewheel) */ \ |
|
/* if the mouse is over it */ \ |
|
if (mu_mouse_over(ctx, *b)) { ctx->scroll_target = cnt; } \ |
|
} else { \ |
|
cnt->scroll.y = 0; \ |
|
} \ |
|
} while (0) |
|
|
|
|
|
static void scrollbars(mu_Context *ctx, mu_Container *cnt, mu_Rect *body) { |
|
int sz = ctx->style->scrollbar_size; |
|
mu_Vec2 cs = cnt->content_size; |
|
cs.x += ctx->style->padding * 2; |
|
cs.y += ctx->style->padding * 2; |
|
mu_push_clip_rect(ctx, *body); |
|
/* resize body to make room for scrollbars */ |
|
if (cs.y > cnt->body.h) { body->w -= sz; } |
|
if (cs.x > cnt->body.w) { body->h -= sz; } |
|
/* to create a horizontal or vertical scrollbar almost-identical code is |
|
** used; only the references to `x|y` `w|h` need to be switched */ |
|
scrollbar(ctx, cnt, body, cs, x, y, w, h); |
|
scrollbar(ctx, cnt, body, cs, y, x, h, w); |
|
mu_pop_clip_rect(ctx); |
|
} |
|
|
|
|
|
static void push_container_body( |
|
mu_Context *ctx, mu_Container *cnt, mu_Rect body, int opt |
|
) { |
|
if (~opt & MU_OPT_NOSCROLL) { scrollbars(ctx, cnt, &body); } |
|
push_layout(ctx, expand_rect(body, -ctx->style->padding), cnt->scroll); |
|
cnt->body = body; |
|
} |
|
|
|
|
|
static void begin_root_container(mu_Context *ctx, mu_Container *cnt) { |
|
push(ctx->container_stack, cnt); |
|
/* push container to roots list and push head command */ |
|
push(ctx->root_list, cnt); |
|
cnt->head = push_jump(ctx, NULL); |
|
/* set as hover root if the mouse is overlapping this container and it has a |
|
** higher zindex than the current hover root */ |
|
if (rect_overlaps_vec2(cnt->rect, ctx->mouse_pos) && |
|
(!ctx->next_hover_root || cnt->zindex > ctx->next_hover_root->zindex) |
|
) { |
|
ctx->next_hover_root = cnt; |
|
} |
|
/* clipping is reset here in case a root-container is made within |
|
** another root-containers's begin/end block; this prevents the inner |
|
** root-container being clipped to the outer */ |
|
push(ctx->clip_stack, unclipped_rect); |
|
} |
|
|
|
|
|
static void end_root_container(mu_Context *ctx) { |
|
/* push tail 'goto' jump command and set head 'skip' command. the final steps |
|
** on initing these are done in mu_end() */ |
|
mu_Container *cnt = mu_get_current_container(ctx); |
|
cnt->tail = push_jump(ctx, NULL); |
|
cnt->head->jump.dst = ctx->command_list.items + ctx->command_list.idx; |
|
/* pop base clip rect and container */ |
|
mu_pop_clip_rect(ctx); |
|
pop_container(ctx); |
|
} |
|
|
|
|
|
int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt) { |
|
mu_Rect body; |
|
mu_Id id = mu_get_id(ctx, title, strlen(title)); |
|
mu_Container *cnt = get_container(ctx, id, opt); |
|
if (!cnt || !cnt->open) { return 0; } |
|
push(ctx->id_stack, id); |
|
|
|
if (cnt->rect.w == 0) { cnt->rect = rect; } |
|
begin_root_container(ctx, cnt); |
|
rect = body = cnt->rect; |
|
|
|
/* draw frame */ |
|
if (~opt & MU_OPT_NOFRAME) { |
|
ctx->draw_frame(ctx, rect, MU_COLOR_WINDOWBG); |
|
} |
|
|
|
/* do title bar */ |
|
if (~opt & MU_OPT_NOTITLE) { |
|
mu_Rect tr = rect; |
|
tr.h = ctx->style->title_height; |
|
ctx->draw_frame(ctx, tr, MU_COLOR_TITLEBG); |
|
|
|
/* do title text */ |
|
if (~opt & MU_OPT_NOTITLE) { |
|
mu_Id id = mu_get_id(ctx, "!title", 6); |
|
mu_update_control(ctx, id, tr, opt); |
|
mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt); |
|
if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { |
|
cnt->rect.x += ctx->mouse_delta.x; |
|
cnt->rect.y += ctx->mouse_delta.y; |
|
} |
|
body.y += tr.h; |
|
body.h -= tr.h; |
|
} |
|
|
|
/* do `close` button */ |
|
if (~opt & MU_OPT_NOCLOSE) { |
|
mu_Id id = mu_get_id(ctx, "!close", 6); |
|
mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h); |
|
tr.w -= r.w; |
|
mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]); |
|
mu_update_control(ctx, id, r, opt); |
|
if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) { |
|
cnt->open = 0; |
|
} |
|
} |
|
} |
|
|
|
push_container_body(ctx, cnt, body, opt); |
|
|
|
/* do `resize` handle */ |
|
if (~opt & MU_OPT_NORESIZE) { |
|
int sz = ctx->style->title_height; |
|
mu_Id id = mu_get_id(ctx, "!resize", 7); |
|
mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz); |
|
mu_update_control(ctx, id, r, opt); |
|
if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) { |
|
cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x); |
|
cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y); |
|
} |
|
} |
|
|
|
/* resize to content size */ |
|
if (opt & MU_OPT_AUTOSIZE) { |
|
mu_Rect r = get_layout(ctx)->body; |
|
cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w); |
|
cnt->rect.h = cnt->content_size.y + (cnt->rect.h - r.h); |
|
} |
|
|
|
/* close if this is a popup window and elsewhere was clicked */ |
|
if (opt & MU_OPT_POPUP && ctx->mouse_pressed && ctx->hover_root != cnt) { |
|
cnt->open = 0; |
|
} |
|
|
|
mu_push_clip_rect(ctx, cnt->body); |
|
return MU_RES_ACTIVE; |
|
} |
|
|
|
|
|
void mu_end_window(mu_Context *ctx) { |
|
mu_pop_clip_rect(ctx); |
|
end_root_container(ctx); |
|
} |
|
|
|
|
|
void mu_open_popup(mu_Context *ctx, const char *name) { |
|
mu_Container *cnt = mu_get_container(ctx, name); |
|
/* set as hover root so popup isn't closed in begin_window_ex() */ |
|
ctx->hover_root = ctx->next_hover_root = cnt; |
|
/* position at mouse cursor, open and bring-to-front */ |
|
cnt->rect = mu_rect(ctx->mouse_pos.x, ctx->mouse_pos.y, 1, 1); |
|
cnt->open = 1; |
|
mu_bring_to_front(ctx, cnt); |
|
} |
|
|
|
|
|
int mu_begin_popup(mu_Context *ctx, const char *name) { |
|
int opt = MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE | |
|
MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED; |
|
return mu_begin_window_ex(ctx, name, mu_rect(0, 0, 0, 0), opt); |
|
} |
|
|
|
|
|
void mu_end_popup(mu_Context *ctx) { |
|
mu_end_window(ctx); |
|
} |
|
|
|
|
|
void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt) { |
|
mu_Container *cnt; |
|
mu_push_id(ctx, name, strlen(name)); |
|
cnt = get_container(ctx, ctx->last_id, opt); |
|
cnt->rect = mu_layout_next(ctx); |
|
if (~opt & MU_OPT_NOFRAME) { |
|
ctx->draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG); |
|
} |
|
push(ctx->container_stack, cnt); |
|
push_container_body(ctx, cnt, cnt->rect, opt); |
|
mu_push_clip_rect(ctx, cnt->body); |
|
} |
|
|
|
|
|
void mu_end_panel(mu_Context *ctx) { |
|
mu_pop_clip_rect(ctx); |
|
pop_container(ctx); |
|
}
|
|
|