mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-13 00:24:12 +00:00
48cc4db45d
It's currently hard-coded to black, but it makes the windows much easier to see when overlapping.
1126 lines
30 KiB
C
1126 lines
30 KiB
C
/*
|
|
imui.c
|
|
|
|
Immediate mode user inferface
|
|
|
|
Copyright (C) 2023 Bill Currie <bill@taniwha.org>
|
|
|
|
Author: Bill Currie <bill@taniwha.org>
|
|
Date: 2023/07/01
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to:
|
|
|
|
Free Software Foundation, Inc.
|
|
59 Temple Place - Suite 330
|
|
Boston, MA 02111-1307, USA
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "QF/darray.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/ecs.h"
|
|
#include "QF/hash.h"
|
|
#include "QF/heapsort.h"
|
|
#include "QF/mathlib.h"
|
|
#include "QF/progs.h"
|
|
#include "QF/quakeio.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "QF/input/event.h"
|
|
|
|
#include "QF/ui/canvas.h"
|
|
#include "QF/ui/font.h"
|
|
#include "QF/ui/imui.h"
|
|
#include "QF/ui/shaper.h"
|
|
#include "QF/ui/text.h"
|
|
|
|
#define c_percent_x (ctx->csys.imui_base + imui_percent_x)
|
|
#define c_percent_y (ctx->csys.imui_base + imui_percent_y)
|
|
#define c_glyphs (ctx->csys.base + canvas_glyphs)
|
|
#define c_passage_glyphs (ctx->csys.text_base + text_passage_glyphs)
|
|
#define c_color (ctx->tsys.text_base + text_color)
|
|
#define c_fill (ctx->csys.base + canvas_fill)
|
|
|
|
#define imui_draw_group ((1 << 15) - 1)
|
|
#define imui_ontop ((1 << 15) - 1)
|
|
#define imui_onbottom (-(1 << 15) + 1)
|
|
|
|
const component_t imui_components[imui_comp_count] = {
|
|
[imui_percent_x] = {
|
|
.size = sizeof (int),
|
|
.name = "percent x",
|
|
},
|
|
[imui_percent_y] = {
|
|
.size = sizeof (int),
|
|
.name = "percent y",
|
|
},
|
|
};
|
|
|
|
typedef struct imui_state_s {
|
|
struct imui_state_s *next;
|
|
struct imui_state_s **prev;
|
|
char *label;
|
|
uint32_t label_len;
|
|
int key_offset;
|
|
int16_t draw_order; // for window canvases
|
|
uint32_t frame_count;
|
|
uint32_t entity;
|
|
} imui_state_t;
|
|
|
|
struct imui_ctx_s {
|
|
canvas_system_t csys;
|
|
uint32_t canvas;
|
|
ecs_system_t vsys;
|
|
text_system_t tsys;
|
|
|
|
text_shaper_t *shaper;
|
|
|
|
hashctx_t *hashctx;
|
|
hashtab_t *tab;
|
|
PR_RESMAP (imui_state_t) state_map;
|
|
imui_state_t *states;
|
|
font_t *font;
|
|
|
|
int64_t frame_start;
|
|
int64_t frame_draw;
|
|
int64_t frame_end;
|
|
uint32_t frame_count;
|
|
|
|
view_t root_view;
|
|
view_t current_parent;
|
|
struct DARRAY_TYPE(view_t) parent_stack;
|
|
struct DARRAY_TYPE(imui_state_t *) windows;
|
|
int16_t draw_order;
|
|
|
|
dstring_t *dstr;
|
|
|
|
uint32_t hot;
|
|
uint32_t active;
|
|
view_pos_t mouse_active;
|
|
bool mouse_pressed;
|
|
bool mouse_released;
|
|
unsigned mouse_buttons;
|
|
view_pos_t mouse_position;
|
|
unsigned shift;
|
|
int key_code;
|
|
int unicode;
|
|
|
|
imui_style_t style;
|
|
struct DARRAY_TYPE(imui_style_t) style_stack;
|
|
};
|
|
|
|
static imui_state_t *
|
|
imui_state_new (imui_ctx_t *ctx)
|
|
{
|
|
imui_state_t *state = PR_RESNEW (ctx->state_map);
|
|
*state = (imui_state_t) {
|
|
.next = ctx->states,
|
|
.prev = &ctx->states,
|
|
.entity = nullent,
|
|
};
|
|
if (ctx->states) {
|
|
ctx->states->prev = &state->next;
|
|
}
|
|
ctx->states = state;
|
|
return state;
|
|
}
|
|
|
|
static void
|
|
imui_state_free (imui_ctx_t *ctx, imui_state_t *state)
|
|
{
|
|
if (state->next) {
|
|
state->next->prev = state->prev;
|
|
}
|
|
*state->prev = state->next;
|
|
PR_RESFREE (ctx->state_map, state);
|
|
}
|
|
|
|
static imui_state_t *
|
|
imui_get_state (imui_ctx_t *ctx, const char *label)
|
|
{
|
|
int key_offset = 0;
|
|
uint32_t label_len = ~0u;
|
|
const char *key = strstr (label, "##");
|
|
if (key) {
|
|
label_len = key - label;
|
|
// key is '###': hash only past this
|
|
if (key[2] == '#') {
|
|
key_offset = (key += 3) - label;
|
|
}
|
|
}
|
|
imui_state_t *state = Hash_Find (ctx->tab, label + key_offset);
|
|
if (state) {
|
|
state->frame_count = ctx->frame_count;
|
|
return state;
|
|
}
|
|
state = imui_state_new (ctx);
|
|
state->label = strdup (label);
|
|
state->label_len = label_len == ~0u ? strlen (label) : label_len;
|
|
state->key_offset = key_offset;
|
|
state->frame_count = ctx->frame_count;
|
|
Hash_Add (ctx->tab, state);
|
|
return state;
|
|
}
|
|
|
|
static const char *
|
|
imui_state_getkey (const void *obj, void *data)
|
|
{
|
|
auto state = (const imui_state_t *) obj;
|
|
return state->label + state->key_offset;
|
|
}
|
|
|
|
imui_ctx_t *
|
|
IMUI_NewContext (canvas_system_t canvas_sys, const char *font, float fontsize)
|
|
{
|
|
imui_ctx_t *ctx = malloc (sizeof (imui_ctx_t));
|
|
uint32_t canvas;
|
|
*ctx = (imui_ctx_t) {
|
|
.csys = canvas_sys,
|
|
.canvas = canvas = Canvas_New (canvas_sys),
|
|
.vsys = { canvas_sys.reg, canvas_sys.view_base },
|
|
.tsys = { canvas_sys.reg, canvas_sys.view_base, canvas_sys.text_base },
|
|
.shaper = Shaper_New (),
|
|
.root_view = Canvas_GetRootView (canvas_sys, canvas),
|
|
.parent_stack = DARRAY_STATIC_INIT (8),
|
|
.windows = DARRAY_STATIC_INIT (8),
|
|
.dstr = dstring_newstr (),
|
|
.hot = nullent,
|
|
.active = nullent,
|
|
.mouse_position = {-1, -1},
|
|
.style_stack = DARRAY_STATIC_INIT (8),
|
|
.style = {
|
|
.background = {
|
|
.normal = 0x04,
|
|
.hot = 0x04,
|
|
.active = 0x04,
|
|
},
|
|
.foreground = {
|
|
.normal = 0x08,
|
|
.hot = 0x0f,
|
|
.active = 0x0c,
|
|
},
|
|
.text = {
|
|
.normal = 0xfe,
|
|
.hot = 0x5f,
|
|
.active = 0x6f,
|
|
},
|
|
},
|
|
};
|
|
*Canvas_DrawGroup (ctx->csys, ctx->canvas) = imui_draw_group;
|
|
ctx->tab = Hash_NewTable (511, imui_state_getkey, 0, ctx, &ctx->hashctx);
|
|
ctx->current_parent = ctx->root_view;
|
|
|
|
auto fpath = Font_SystemFont (font);
|
|
if (fpath) {
|
|
QFile *file = Qopen (fpath, "rb");
|
|
if (file) {
|
|
ctx->font = Font_Load (file, fontsize);
|
|
//Qclose (file); FIXME closed by QFS_LoadFile
|
|
}
|
|
free (fpath);
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
void
|
|
IMUI_DestroyContext (imui_ctx_t *ctx)
|
|
{
|
|
for (auto s = ctx->states; s; s = s->next) {
|
|
free (s->label);
|
|
}
|
|
PR_RESDELMAP (ctx->state_map);
|
|
|
|
if (ctx->font) {
|
|
Font_Free (ctx->font);
|
|
}
|
|
|
|
DARRAY_CLEAR (&ctx->parent_stack);
|
|
DARRAY_CLEAR (&ctx->windows);
|
|
DARRAY_CLEAR (&ctx->style_stack);
|
|
dstring_delete (ctx->dstr);
|
|
|
|
Hash_DelTable (ctx->tab);
|
|
Hash_DelContext (ctx->hashctx);
|
|
Shaper_Delete (ctx->shaper);
|
|
free (ctx);
|
|
}
|
|
|
|
void
|
|
IMUI_SetVisible (imui_ctx_t *ctx, bool visible)
|
|
{
|
|
*Canvas_Visible (ctx->csys, ctx->canvas) = visible;
|
|
for (uint32_t i = 0; i < ctx->windows.size; i++) {
|
|
*Canvas_Visible (ctx->csys, ctx->windows.a[i]->entity) = visible;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_SetSize (imui_ctx_t *ctx, int xlen, int ylen)
|
|
{
|
|
Canvas_SetLen (ctx->csys, ctx->canvas, (view_pos_t) { xlen, ylen });
|
|
}
|
|
|
|
void
|
|
IMUI_ProcessEvent (imui_ctx_t *ctx, const IE_event_t *ie_event)
|
|
{
|
|
if (ie_event->type == ie_mouse) {
|
|
auto m = &ie_event->mouse;
|
|
ctx->mouse_position = (view_pos_t) { m->x, m->y };
|
|
|
|
unsigned old = ctx->mouse_buttons & 1;
|
|
unsigned new = m->buttons & 1;
|
|
ctx->mouse_pressed = (old ^ new) & new;
|
|
ctx->mouse_released = (old ^ new) & !new;
|
|
ctx->mouse_buttons = m->buttons;
|
|
} else {
|
|
auto k = &ie_event->key;
|
|
//printf ("imui: %d %d %x\n", k->code, k->unicode, k->shift);
|
|
ctx->shift = k->shift;
|
|
ctx->key_code = k->code;
|
|
ctx->unicode = k->unicode;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_BeginFrame (imui_ctx_t *ctx)
|
|
{
|
|
Shaper_FlushUnused (ctx->shaper);
|
|
uint32_t root_ent = ctx->root_view.id;
|
|
auto root_size = View_GetLen (ctx->root_view);
|
|
Ent_RemoveComponent (root_ent, ctx->root_view.comp, ctx->root_view.reg);
|
|
ctx->root_view = View_AddToEntity (root_ent, ctx->vsys, nullview);
|
|
auto ref = View_GetRef (ctx->root_view);
|
|
Hierarchy_SetTreeMode (ref->hierarchy, true);
|
|
View_SetLen (ctx->root_view, root_size.x, root_size.y);
|
|
ctx->frame_start = Sys_LongTime ();
|
|
ctx->frame_count++;
|
|
ctx->current_parent = ctx->root_view;
|
|
for (uint32_t i = 0; i < ctx->windows.size; i++) {
|
|
auto window = View_FromEntity (ctx->vsys, ctx->windows.a[i]->entity);
|
|
View_Delete (window);
|
|
}
|
|
ctx->draw_order = ctx->windows.size;
|
|
DARRAY_RESIZE (&ctx->parent_stack, 0);
|
|
DARRAY_RESIZE (&ctx->windows, 0);
|
|
DARRAY_RESIZE (&ctx->style_stack, 0);
|
|
}
|
|
|
|
static void
|
|
prune_objects (imui_ctx_t *ctx)
|
|
{
|
|
for (auto s = &ctx->states; *s; ) {
|
|
if ((*s)->frame_count == ctx->frame_count) {
|
|
s = &(*s)->next;
|
|
} else {
|
|
Hash_Del (ctx->tab, (*s)->label + (*s)->key_offset);
|
|
free ((*s)->label);
|
|
imui_state_free (ctx, *s);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define DFL "\e[39;49m"
|
|
#define BLK "\e[30;40m"
|
|
#define RED "\e[31;40m"
|
|
#define GRN "\e[32;40m"
|
|
#define ONG "\e[33;40m"
|
|
#define BLU "\e[34;40m"
|
|
#define MAG "\e[35;40m"
|
|
#define CYN "\e[36;40m"
|
|
#define WHT "\e[37;40m"
|
|
|
|
static const char *
|
|
view_color (hierarchy_t *h, uint32_t ind, imui_ctx_t *ctx, bool for_y)
|
|
{
|
|
auto reg = h->reg;
|
|
uint32_t e = h->ent[ind];
|
|
viewcont_t *cont = h->components[view_control];
|
|
|
|
auto semantic = (imui_size_t) cont[ind].semantic_x;
|
|
if (for_y) {
|
|
semantic = cont[ind].semantic_y;
|
|
}
|
|
|
|
switch (semantic) {
|
|
case imui_size_none:
|
|
if (Ent_HasComponent (e, c_glyphs, reg)) {
|
|
return CYN;
|
|
}
|
|
return DFL;
|
|
case imui_size_pixels: return GRN;
|
|
case imui_size_fittext: return CYN;
|
|
case imui_size_percent: return ONG;
|
|
case imui_size_fitchildren: return MAG;
|
|
case imui_size_expand: return RED;
|
|
}
|
|
return BLU;
|
|
}
|
|
|
|
static void __attribute__((used))
|
|
dump_tree (hierarchy_t *h, uint32_t ind, int level, imui_ctx_t *ctx)
|
|
{
|
|
view_pos_t *len = h->components[view_len];
|
|
auto c = ((viewcont_t *)h->components[view_control])[ind];
|
|
uint32_t e = h->ent[ind];
|
|
printf ("%2d: %*s[%s%d %s%d"DFL"] %c %s%d %s%d"DFL, ind,
|
|
level * 3, "",
|
|
view_color (h, ind, ctx, false), len[ind].x,
|
|
view_color (h, ind, ctx, true), len[ind].y,
|
|
c.vertical ? 'v' : 'h',
|
|
view_color (h, ind, ctx, false), c.semantic_x,
|
|
view_color (h, ind, ctx, true), c.semantic_y);
|
|
for (uint32_t j = 0; j < h->reg->components.size; j++) {
|
|
if (Ent_HasComponent (e, j, h->reg)) {
|
|
printf (", %s", h->reg->components.a[j].name);
|
|
if (j == c_percent_x || j == c_percent_y) {
|
|
auto val = *(int *) Ent_GetComponent (e, j, h->reg);
|
|
printf ("(%s%d"DFL")", view_color (h, ind, ctx,
|
|
j == c_percent_y), val);
|
|
}
|
|
}
|
|
}
|
|
printf (DFL"\n");
|
|
|
|
if (h->childIndex[ind] > ind) {
|
|
for (uint32_t i = 0; i < h->childCount[ind]; i++) {
|
|
if (h->childIndex[ind] + i >= h->num_objects) {
|
|
break;
|
|
}
|
|
dump_tree (h, h->childIndex[ind] + i, level + 1, ctx);
|
|
}
|
|
}
|
|
if (!level) {
|
|
puts ("");
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
bool x, y;
|
|
} boolpair_t;
|
|
|
|
static void
|
|
calc_upwards_dependent (imui_ctx_t *ctx, hierarchy_t *h,
|
|
boolpair_t *down_depend)
|
|
{
|
|
auto reg = ctx->csys.reg;
|
|
uint32_t *ent = h->ent;
|
|
view_pos_t *len = h->components[view_len];
|
|
viewcont_t *cont = h->components[view_control];
|
|
uint32_t *parent = h->parentIndex;
|
|
for (uint32_t i = 1; i < h->num_objects; i++) {
|
|
if (down_depend
|
|
&& (cont[i].semantic_x == imui_size_fitchildren
|
|
|| cont[i].semantic_x == imui_size_expand)) {
|
|
down_depend[i].x = true;
|
|
} else if ((!down_depend
|
|
|| !(down_depend[i].x = down_depend[parent[i]].x))
|
|
&& cont[i].semantic_x == imui_size_percent) {
|
|
int *percent = Ent_GetComponent (ent[i], c_percent_x, reg);
|
|
int x = (len[parent[i]].x * *percent) / 100;
|
|
len[i].x = x;
|
|
}
|
|
if (down_depend
|
|
&& (cont[i].semantic_y == imui_size_fitchildren
|
|
|| cont[i].semantic_y == imui_size_expand)) {
|
|
down_depend[i].y = true;
|
|
} else if ((!down_depend
|
|
|| !(down_depend[i].y = down_depend[parent[i]].y))
|
|
&& cont[i].semantic_y == imui_size_percent) {
|
|
int *percent = Ent_GetComponent (ent[i], c_percent_y, reg);
|
|
int y = (len[parent[i]].y * *percent) / 100;
|
|
len[i].y = y;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
calc_downwards_dependent (hierarchy_t *h)
|
|
{
|
|
view_pos_t *len = h->components[view_len];
|
|
viewcont_t *cont = h->components[view_control];
|
|
for (uint32_t i = h->num_objects; --i > 0; ) {
|
|
view_pos_t clen = len[i];
|
|
if (cont[i].semantic_x == imui_size_fitchildren
|
|
|| cont[i].semantic_x == imui_size_expand) {
|
|
clen.x = 0;
|
|
if (cont[i].vertical) {
|
|
for (uint32_t j = 0; j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
clen.x = max (clen.x, len[child].x);
|
|
}
|
|
} else {
|
|
for (uint32_t j = 0; j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
clen.x += len[child].x;
|
|
}
|
|
}
|
|
}
|
|
if (cont[i].semantic_y == imui_size_fitchildren
|
|
|| cont[i].semantic_y == imui_size_expand) {
|
|
clen.y = 0;
|
|
if (!cont[i].vertical) {
|
|
for (uint32_t j = 0; j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
clen.y = max (clen.y, len[child].y);
|
|
}
|
|
} else {
|
|
for (uint32_t j = 0; j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
clen.y += len[child].y;
|
|
}
|
|
}
|
|
}
|
|
len[i] = clen;
|
|
}
|
|
}
|
|
|
|
static void
|
|
calc_expansions (imui_ctx_t *ctx, hierarchy_t *h)
|
|
{
|
|
auto reg = ctx->csys.reg;
|
|
|
|
uint32_t *ent = h->ent;
|
|
view_pos_t *len = h->components[view_len];
|
|
viewcont_t *cont = h->components[view_control];
|
|
|
|
for (uint32_t i = 0; i < h->num_objects; i++) {
|
|
view_pos_t tlen = {};
|
|
view_pos_t elen = {};
|
|
view_pos_t ecount = {};
|
|
for (uint32_t j = 0; j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
tlen.x += len[child].x;
|
|
tlen.y += len[child].y;
|
|
if (cont[child].semantic_x == imui_size_expand) {
|
|
int *p = Ent_GetComponent (ent[child], c_percent_x, reg);
|
|
elen.x += *p;
|
|
ecount.x++;
|
|
}
|
|
if (cont[child].semantic_y == imui_size_expand) {
|
|
int *p = Ent_GetComponent (ent[child], c_percent_y, reg);
|
|
elen.y += *p;
|
|
ecount.y++;
|
|
}
|
|
}
|
|
// printf ("i:%d t:[%d %d] e:[%d %d] ec:[%d %d]\n", i, tlen.x, tlen.y,
|
|
// elen.x, elen.y, ecount.x, ecount.y);
|
|
for (uint32_t j = 0; ecount.x && j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
if (cont[child].semantic_x != imui_size_expand) {
|
|
continue;
|
|
}
|
|
if (cont[i].vertical) {
|
|
len[child].x = len[i].x;
|
|
// printf ("xc:%d p:%d l:%d\n", child, len[i].x, len[child].x);
|
|
} else {
|
|
int space = len[i].x - tlen.x;
|
|
int *p = Ent_GetComponent (ent[child], c_percent_x, reg);
|
|
int delta = *p * space / elen.x;
|
|
len[child].x += max (delta, 0);
|
|
// printf ("xc:%d p:%d s:%d e:%d: l:%d\n", child, *p, space,
|
|
// elen.x, len[child].x);
|
|
}
|
|
}
|
|
for (uint32_t j = 0; ecount.y && j < h->childCount[i]; j++) {
|
|
uint32_t child = h->childIndex[i] + j;
|
|
if (cont[child].semantic_y != imui_size_expand) {
|
|
continue;
|
|
}
|
|
if (cont[i].vertical) {
|
|
int space = len[i].y - tlen.y;
|
|
int *p = Ent_GetComponent (ent[child], c_percent_y, reg);
|
|
int delta = *p * space / elen.y;
|
|
len[child].y += max (delta, 0);
|
|
// printf ("yc:%d p:%d s:%d e:%d: l:%d\n", child, *p, space,
|
|
// elen.y, len[child].y);
|
|
} else {
|
|
len[child].y = len[i].y;
|
|
// printf ("yc:%d p:%d l:%d\n", child, len[i].y, len[child].y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//FIXME currently works properly only for grav_northwest
|
|
static void
|
|
layout_objects (imui_ctx_t *ctx, view_t root_view)
|
|
{
|
|
auto ref = View_GetRef (root_view);
|
|
auto h = ref->hierarchy;
|
|
|
|
view_pos_t *pos = h->components[view_pos];
|
|
view_pos_t *len = h->components[view_len];
|
|
viewcont_t *cont = h->components[view_control];
|
|
uint32_t *parent = h->parentIndex;
|
|
boolpair_t down_depend[h->num_objects];
|
|
|
|
// the root view size is always explicit
|
|
down_depend[0] = (boolpair_t) { false, false };
|
|
calc_upwards_dependent (ctx, h, down_depend);
|
|
calc_downwards_dependent (h);
|
|
calc_expansions (ctx, h);
|
|
//dump_tree (h, 0, 0, ctx);
|
|
// resolve conflicts
|
|
//fflush (stdout);
|
|
|
|
view_pos_t cpos = {};
|
|
uint32_t cur_parent = 0;
|
|
for (uint32_t i = 1; i < h->num_objects; i++) {
|
|
if (parent[i] != cur_parent) {
|
|
cur_parent = parent[i];
|
|
cpos = (view_pos_t) {};
|
|
}
|
|
if (!cont[i].free_x && !cont[i].free_y) {
|
|
pos[i] = cpos;
|
|
} else if (!cont[i].free_x) {
|
|
pos[i].x = cpos.x;
|
|
} else if (!cont[i].free_y) {
|
|
pos[i].y = cpos.y;
|
|
}
|
|
if (cont[parent[i]].vertical) {
|
|
cpos.y += cont[i].semantic_y == imui_size_none ? 0 : len[i].y;
|
|
} else {
|
|
cpos.x += cont[i].semantic_x == imui_size_none ? 0 : len[i].x;
|
|
}
|
|
}
|
|
|
|
View_UpdateHierarchy (root_view);
|
|
}
|
|
|
|
static void
|
|
check_inside (imui_ctx_t *ctx, view_t root_view)
|
|
{
|
|
auto ref = View_GetRef (root_view);
|
|
auto h = ref->hierarchy;
|
|
|
|
uint32_t *entity = h->ent;
|
|
view_pos_t *abs = h->components[view_abs];
|
|
view_pos_t *len = h->components[view_len];
|
|
viewcont_t *cont = h->components[view_control];
|
|
auto mp = ctx->mouse_position;
|
|
|
|
for (uint32_t i = 0; i < h->num_objects; i++) {
|
|
if (cont[i].active
|
|
&& mp.x >= abs[i].x && mp.y >= abs[i].y
|
|
&& mp.x < abs[i].x + len[i].x && mp.y < abs[i].y + len[i].y) {
|
|
if (ctx->active == entity[i] || ctx->active == nullent) {
|
|
ctx->hot = entity[i];
|
|
}
|
|
}
|
|
}
|
|
//printf ("check_inside: %8x %8x\n", ctx->hot, ctx->active);
|
|
}
|
|
|
|
static int
|
|
imui_window_cmp (const void *a, const void *b)
|
|
{
|
|
auto windowa = *(imui_state_t **) a;
|
|
auto windowb = *(imui_state_t **) b;
|
|
return windowa->draw_order - windowb->draw_order;
|
|
}
|
|
|
|
static void
|
|
sort_windows (imui_ctx_t *ctx)
|
|
{
|
|
heapsort (ctx->windows.a, ctx->windows.size, sizeof (imui_state_t *),
|
|
imui_window_cmp);
|
|
for (uint32_t i = 0; i < ctx->windows.size; i++) {
|
|
auto window = ctx->windows.a[i];
|
|
window->draw_order = i + 1;
|
|
*Canvas_DrawOrder (ctx->csys, window->entity) = window->draw_order;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_Draw (imui_ctx_t *ctx)
|
|
{
|
|
ctx->frame_draw = Sys_LongTime ();
|
|
sort_windows (ctx);
|
|
auto ref = View_GetRef (ctx->root_view);
|
|
Hierarchy_SetTreeMode (ref->hierarchy, false);
|
|
for (uint32_t i = 0; i < ctx->windows.size; i++) {
|
|
auto window = View_FromEntity (ctx->vsys, ctx->windows.a[i]->entity);
|
|
auto ref = View_GetRef (window);
|
|
Hierarchy_SetTreeMode (ref->hierarchy, false);
|
|
}
|
|
Canvas_DrawSort (ctx->csys);
|
|
|
|
prune_objects (ctx);
|
|
layout_objects (ctx, ctx->root_view);
|
|
for (uint32_t i = 0; i < ctx->windows.size; i++) {
|
|
auto window = View_FromEntity (ctx->vsys, ctx->windows.a[i]->entity);
|
|
layout_objects (ctx, window);
|
|
}
|
|
ctx->hot = nullent;
|
|
check_inside (ctx, ctx->root_view);
|
|
for (uint32_t i = 0; i < ctx->windows.size; i++) {
|
|
auto window = View_FromEntity (ctx->vsys, ctx->windows.a[i]->entity);
|
|
check_inside (ctx, window);
|
|
}
|
|
|
|
ctx->frame_end = Sys_LongTime ();
|
|
}
|
|
|
|
void
|
|
IMUI_PushLayout (imui_ctx_t *ctx, bool vertical)
|
|
{
|
|
DARRAY_APPEND (&ctx->parent_stack, ctx->current_parent);
|
|
auto view = View_New (ctx->vsys, ctx->current_parent);
|
|
auto pcont = View_Control (ctx->current_parent);
|
|
ctx->current_parent = view;
|
|
auto x_size = pcont->vertical ? imui_size_expand
|
|
: imui_size_fitchildren;
|
|
auto y_size = pcont->vertical ? imui_size_fitchildren
|
|
: imui_size_expand;
|
|
*View_Control (view) = (viewcont_t) {
|
|
.gravity = grav_northwest,
|
|
.visible = 1,
|
|
.semantic_x = x_size,
|
|
.semantic_y = y_size,
|
|
.vertical = vertical,
|
|
};
|
|
View_SetLen (view, 0, 0);
|
|
if (x_size == imui_size_expand) {
|
|
*(int*) Ent_AddComponent (view.id, c_percent_x, ctx->csys.reg) = 100;
|
|
}
|
|
if (y_size == imui_size_expand) {
|
|
*(int*) Ent_AddComponent (view.id, c_percent_y, ctx->csys.reg) = 100;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_PopLayout (imui_ctx_t *ctx)
|
|
{
|
|
ctx->current_parent = DARRAY_REMOVE (&ctx->parent_stack);
|
|
}
|
|
|
|
void
|
|
IMUI_Layout_SetXSize (imui_ctx_t *ctx, imui_size_t size, int value)
|
|
{
|
|
auto pcont = View_Control (ctx->current_parent);
|
|
uint32_t id = ctx->current_parent.id;
|
|
pcont->semantic_x = size;
|
|
if (size == imui_size_percent || imui_size_expand) {
|
|
*(int *) Ent_AddComponent(id, c_percent_x, ctx->csys.reg) = value;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_Layout_SetYSize (imui_ctx_t *ctx, imui_size_t size, int value)
|
|
{
|
|
auto pcont = View_Control (ctx->current_parent);
|
|
uint32_t id = ctx->current_parent.id;
|
|
pcont->semantic_y = size;
|
|
if (size == imui_size_percent || imui_size_expand) {
|
|
*(int *) Ent_AddComponent(id, c_percent_y, ctx->csys.reg) = value;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_PushStyle (imui_ctx_t *ctx, const imui_style_t *style)
|
|
{
|
|
DARRAY_APPEND (&ctx->style_stack, ctx->style);
|
|
if (style) {
|
|
ctx->style = *style;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_PopStyle (imui_ctx_t *ctx)
|
|
{
|
|
ctx->style = DARRAY_REMOVE (&ctx->style_stack);
|
|
}
|
|
|
|
void
|
|
IMUI_Style_Update (imui_ctx_t *ctx, const imui_style_t *style)
|
|
{
|
|
ctx->style = *style;
|
|
}
|
|
|
|
void
|
|
IMUI_Style_Fetch (const imui_ctx_t *ctx, imui_style_t *style)
|
|
{
|
|
*style = ctx->style;
|
|
}
|
|
|
|
static bool
|
|
check_button_state (imui_ctx_t *ctx, uint32_t entity)
|
|
{
|
|
bool result = false;
|
|
//printf ("check_button_state: h:%8x a:%8x e:%8x\n", ctx->hot, ctx->active, entity);
|
|
if (ctx->active == entity) {
|
|
if (ctx->mouse_released) {
|
|
result = ctx->hot == entity;
|
|
ctx->active = nullent;
|
|
}
|
|
} else if (ctx->hot == entity) {
|
|
if (ctx->mouse_pressed) {
|
|
ctx->active = entity;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static view_pos_t
|
|
check_drag_delta (imui_ctx_t *ctx, uint32_t entity)
|
|
{
|
|
view_pos_t delta = {};
|
|
if (ctx->active == entity) {
|
|
delta.x = ctx->mouse_position.x - ctx->mouse_active.x;
|
|
delta.y = ctx->mouse_position.y - ctx->mouse_active.y;
|
|
ctx->mouse_active = ctx->mouse_position;
|
|
if (ctx->mouse_released) {
|
|
ctx->active = nullent;
|
|
}
|
|
} else if (ctx->hot == entity) {
|
|
if (ctx->mouse_pressed) {
|
|
ctx->mouse_active = ctx->mouse_position;
|
|
ctx->active = entity;
|
|
}
|
|
}
|
|
return delta;
|
|
}
|
|
|
|
static view_t
|
|
add_text (imui_ctx_t *ctx, view_t view, imui_state_t *state, int mode)
|
|
{
|
|
auto reg = ctx->csys.reg;
|
|
|
|
auto text = Text_StringView (ctx->tsys, view, ctx->font,
|
|
state->label, state->label_len, 0, 0,
|
|
ctx->shaper);
|
|
|
|
int ascender = ctx->font->face->size->metrics.ascender / 64;
|
|
int descender = ctx->font->face->size->metrics.descender / 64;
|
|
auto len = View_GetLen (text);
|
|
View_SetLen (text, len.x, ascender - descender);
|
|
// text is positioned such that 0 is the baseline, and +y offset moves
|
|
// the text down. The font's global ascender is used to find the baseline
|
|
// relative to the top of the view.
|
|
auto pos = View_GetPos (text);
|
|
View_SetPos (text, pos.x, pos.y - len.y + ascender);
|
|
View_SetGravity (text, grav_northwest);
|
|
|
|
View_SetVisible (text, 1);
|
|
Ent_SetComponent (text.id, c_glyphs, reg,
|
|
Ent_GetComponent (text.id, c_passage_glyphs, reg));
|
|
|
|
len = View_GetLen (text);
|
|
View_SetLen (view, len.x, len.y);
|
|
|
|
uint32_t color = ctx->style.text.color[mode];
|
|
*(uint32_t *) Ent_AddComponent (text.id, c_color, ctx->tsys.reg) = color;
|
|
|
|
return text;
|
|
}
|
|
|
|
static int
|
|
update_hot_active (imui_ctx_t *ctx, uint32_t old_entity, uint32_t new_entity)
|
|
{
|
|
int mode = 0;
|
|
if (old_entity != nullent) {
|
|
if (ctx->hot == old_entity) {
|
|
ctx->hot = new_entity;
|
|
mode = 1;
|
|
}
|
|
if (ctx->active == old_entity) {
|
|
ctx->active = new_entity;
|
|
mode = 2;
|
|
}
|
|
}
|
|
return mode;
|
|
}
|
|
|
|
static void
|
|
set_fill (imui_ctx_t *ctx, view_t view, byte color)
|
|
{
|
|
*(byte*) Ent_AddComponent (view.id, c_fill, ctx->csys.reg) = color;
|
|
}
|
|
|
|
static void
|
|
set_control (imui_ctx_t *ctx, view_t view, bool active)
|
|
{
|
|
*View_Control (view) = (viewcont_t) {
|
|
.gravity = grav_northwest,
|
|
.visible = 1,
|
|
.semantic_x = imui_size_pixels,
|
|
.semantic_y = imui_size_pixels,
|
|
.active = active,
|
|
};
|
|
}
|
|
|
|
static void
|
|
set_expand_x (imui_ctx_t *ctx, view_t view, int weight)
|
|
{
|
|
View_Control (view)->semantic_x = imui_size_expand;
|
|
*(int *) Ent_AddComponent(view.id, c_percent_x, ctx->csys.reg) = weight;
|
|
}
|
|
|
|
void
|
|
IMUI_Label (imui_ctx_t *ctx, const char *label)
|
|
{
|
|
auto state = imui_get_state (ctx, label);
|
|
|
|
auto view = View_New (ctx->vsys, ctx->current_parent);
|
|
state->entity = view.id;
|
|
|
|
set_control (ctx, view, true);
|
|
set_fill (ctx, view, ctx->style.background.normal);
|
|
add_text (ctx, view, state, 0);
|
|
}
|
|
|
|
void
|
|
IMUI_Labelf (imui_ctx_t *ctx, const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start (args, fmt);
|
|
dvsprintf (ctx->dstr, fmt, args);
|
|
va_end (args);
|
|
IMUI_Label (ctx, ctx->dstr->str);
|
|
}
|
|
|
|
bool
|
|
IMUI_Button (imui_ctx_t *ctx, const char *label)
|
|
{
|
|
auto state = imui_get_state (ctx, label);
|
|
uint32_t old_entity = state->entity;
|
|
|
|
auto view = View_New (ctx->vsys, ctx->current_parent);
|
|
state->entity = view.id;
|
|
int mode = update_hot_active (ctx, old_entity, state->entity);
|
|
|
|
set_control (ctx, view, true);
|
|
set_fill (ctx, view, ctx->style.foreground.color[mode]);
|
|
add_text (ctx, view, state, mode);
|
|
|
|
return check_button_state (ctx, state->entity);
|
|
}
|
|
|
|
bool
|
|
IMUI_Checkbox (imui_ctx_t *ctx, bool *flag, const char *label)
|
|
{
|
|
auto state = imui_get_state (ctx, label);
|
|
uint32_t old_entity = state->entity;
|
|
|
|
auto view = View_New (ctx->vsys, ctx->current_parent);
|
|
state->entity = view.id;
|
|
int mode = update_hot_active (ctx, old_entity, state->entity);
|
|
|
|
set_control (ctx, view, true);
|
|
View_Control (view)->semantic_x = imui_size_fitchildren;
|
|
View_Control (view)->semantic_y = imui_size_fitchildren;
|
|
|
|
set_fill (ctx, view, ctx->style.background.color[mode]);
|
|
|
|
auto checkbox = View_New (ctx->vsys, view);
|
|
set_control (ctx, checkbox, false);
|
|
View_SetLen (checkbox, 20, 20);
|
|
set_fill (ctx, checkbox, ctx->style.foreground.color[mode]);
|
|
if (!*flag) {
|
|
auto punch = View_New (ctx->vsys, checkbox);
|
|
set_control (ctx, punch, false);
|
|
View_SetGravity (punch, grav_center);
|
|
View_SetLen (punch, 14, 14);
|
|
set_fill (ctx, punch, ctx->style.background.color[mode]);
|
|
}
|
|
|
|
auto text = View_New (ctx->vsys, view);
|
|
set_control (ctx, text, false);
|
|
add_text (ctx, text, state, mode);
|
|
|
|
if (check_button_state (ctx, state->entity)) {
|
|
*flag = !*flag;
|
|
}
|
|
return *flag;
|
|
}
|
|
|
|
void
|
|
IMUI_Radio (imui_ctx_t *ctx, int *curvalue, int value, const char *label)
|
|
{
|
|
auto state = imui_get_state (ctx, label);
|
|
uint32_t old_entity = state->entity;
|
|
|
|
auto view = View_New (ctx->vsys, ctx->current_parent);
|
|
state->entity = view.id;
|
|
int mode = update_hot_active (ctx, old_entity, state->entity);
|
|
|
|
set_control (ctx, view, true);
|
|
View_Control (view)->semantic_x = imui_size_fitchildren;
|
|
View_Control (view)->semantic_y = imui_size_fitchildren;
|
|
|
|
set_fill (ctx, view, ctx->style.background.color[mode]);
|
|
|
|
auto checkbox = View_New (ctx->vsys, view);
|
|
set_control (ctx, checkbox, false);
|
|
View_SetLen (checkbox, 20, 20);
|
|
set_fill (ctx, checkbox, ctx->style.foreground.color[mode]);
|
|
if (*curvalue != value) {
|
|
auto punch = View_New (ctx->vsys, checkbox);
|
|
set_control (ctx, punch, false);
|
|
View_SetGravity (punch, grav_center);
|
|
View_SetLen (punch, 14, 14);
|
|
set_fill (ctx, punch, ctx->style.background.color[mode]);
|
|
}
|
|
|
|
if (state->label_len) {
|
|
auto text = View_New (ctx->vsys, view);
|
|
set_control (ctx, text, false);
|
|
add_text (ctx, text, state, mode);
|
|
}
|
|
|
|
if (check_button_state (ctx, state->entity)) {
|
|
*curvalue = value;
|
|
}
|
|
}
|
|
|
|
void
|
|
IMUI_Slider (imui_ctx_t *ctx, float *value, float minval, float maxval,
|
|
const char *label)
|
|
{
|
|
}
|
|
|
|
void
|
|
IMUI_Spacer (imui_ctx_t *ctx,
|
|
imui_size_t xsize, int xvalue,
|
|
imui_size_t ysize, int yvalue)
|
|
{
|
|
auto view = View_New (ctx->vsys, ctx->current_parent);
|
|
View_SetLen (ctx->current_parent, 0, 0);
|
|
|
|
int xlen = 0;
|
|
int ylen = 0;
|
|
if (xsize == imui_size_pixels) {
|
|
xlen = xvalue;
|
|
}
|
|
if (ysize == imui_size_pixels) {
|
|
ylen = yvalue;
|
|
}
|
|
set_control (ctx, view, false);
|
|
View_SetLen (view, xlen, ylen);
|
|
View_Control (view)->semantic_x = xsize;
|
|
View_Control (view)->semantic_y = ysize;
|
|
auto reg = ctx->csys.reg;
|
|
if (xsize == imui_size_percent || xsize == imui_size_expand) {
|
|
*(int*) Ent_AddComponent (view.id, c_percent_x, reg) = xvalue;
|
|
}
|
|
if (ysize == imui_size_percent || ysize == imui_size_expand) {
|
|
*(int*) Ent_AddComponent (view.id, c_percent_y, reg) = yvalue;
|
|
}
|
|
|
|
set_fill (ctx, view, ctx->style.background.normal);
|
|
}
|
|
|
|
void
|
|
IMUI_StartWindow (imui_ctx_t *ctx, imui_window_t *window)
|
|
{
|
|
if (!window->is_open) {
|
|
return;
|
|
}
|
|
auto state = imui_get_state (ctx, window->name);
|
|
uint32_t old_entity = state->entity;
|
|
|
|
DARRAY_APPEND (&ctx->parent_stack, ctx->current_parent);
|
|
|
|
auto canvas = Canvas_New (ctx->csys);
|
|
*Canvas_DrawGroup (ctx->csys, canvas) = imui_draw_group;
|
|
auto window_view = Canvas_GetRootView (ctx->csys, canvas);
|
|
state->entity = window_view.id;
|
|
int mode = update_hot_active (ctx, old_entity, state->entity);
|
|
auto ref = View_GetRef (window_view);
|
|
Hierarchy_SetTreeMode (ref->hierarchy, true);
|
|
|
|
DARRAY_APPEND (&ctx->windows, state);
|
|
|
|
if (!state->draw_order) {
|
|
state->draw_order = ++ctx->draw_order;
|
|
}
|
|
|
|
ctx->current_parent = window_view;
|
|
*View_Control (window_view) = (viewcont_t) {
|
|
.gravity = grav_northwest,
|
|
.visible = 1,
|
|
.semantic_x = imui_size_none,
|
|
.semantic_y = imui_size_none,
|
|
.free_x = 1,
|
|
.free_y = 1,
|
|
.vertical = true,
|
|
.active = 1,
|
|
};
|
|
View_SetPos (window_view, window->xpos, window->ypos);
|
|
View_SetLen (window_view, window->xlen, window->ylen);
|
|
|
|
#define IMUI_context ctx
|
|
UI_Vertical {
|
|
UI_Horizontal {
|
|
char cbutton = window->is_collapsed ? '>' : 'v';
|
|
if (UI_Button (va (0, "%c##collapse_%s", cbutton, window->name))) {
|
|
window->is_collapsed = !window->is_collapsed;
|
|
}
|
|
|
|
auto tb_state = imui_get_state (ctx, va (0, "%s##title_bar",
|
|
window->name));
|
|
uint32_t tb_old_entity = tb_state->entity;
|
|
auto title_bar = View_New (ctx->vsys, ctx->current_parent);
|
|
tb_state->entity = title_bar.id;
|
|
int tb_mode = update_hot_active (ctx, tb_old_entity, tb_state->entity);
|
|
auto delta = check_drag_delta (ctx, tb_state->entity);
|
|
if (ctx->active == tb_state->entity) {
|
|
state->draw_order = imui_ontop;
|
|
window->xpos += delta.x;
|
|
window->ypos += delta.y;
|
|
}
|
|
|
|
set_control (ctx, title_bar, true);
|
|
set_expand_x (ctx, title_bar, 100);
|
|
set_fill (ctx, title_bar, ctx->style.foreground.color[tb_mode]);
|
|
|
|
auto title = add_text (ctx, title_bar, state, mode);
|
|
View_Control (title)->gravity = grav_center;
|
|
|
|
if (UI_Button (va (0, "X##close_%s", window->name))) {
|
|
window->is_open = false;
|
|
}
|
|
}
|
|
auto bg = ctx->style.background.normal;
|
|
UI_Horizontal {
|
|
ctx->style.background.normal = 0;//FIXME style
|
|
IMUI_Spacer (ctx, imui_size_pixels, 2, imui_size_expand, 100);
|
|
ctx->style.background.normal = bg;
|
|
UI_Vertical {
|
|
IMUI_Layout_SetXSize (ctx, imui_size_expand, 100);
|
|
window_view = ctx->current_parent;
|
|
}
|
|
ctx->style.background.normal = 0;//FIXME style
|
|
IMUI_Spacer (ctx, imui_size_pixels, 2, imui_size_expand, 100);
|
|
ctx->style.background.normal = bg;
|
|
}
|
|
ctx->style.background.normal = 0;//FIXME style
|
|
IMUI_Spacer (ctx, imui_size_expand, 100, imui_size_pixels, 2);
|
|
ctx->style.background.normal = bg;
|
|
}
|
|
#undef IMUI_context
|
|
ctx->current_parent = window_view;
|
|
}
|
|
|
|
void
|
|
IMUI_EndWindow (imui_ctx_t *ctx)
|
|
{
|
|
IMUI_PopLayout (ctx);
|
|
}
|