quakeforge/libs/ui/imui.c
Bill Currie 834587f102 [ui] Update mouse buttons only for button events
This seems to have fixed the sticky mouse. If nothing else, I've at
least established that the problem is in IMUI (it sees the events) and
not in QF's input system.
2024-01-09 09:58:03 +09:00

1986 lines
53 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 IMUI_context ctx
#define c_fraction_x (ctx->csys.imui_base + imui_fraction_x)
#define c_fraction_y (ctx->csys.imui_base + imui_fraction_y)
#define c_reference (ctx->csys.imui_base + imui_reference)
#define t_passage_glyphs (ctx->csys.text_base + text_passage_glyphs)
#define c_passage_glyphs (ctx->csys.base + canvas_passage_glyphs)
#define c_glyphs (ctx->csys.base + canvas_glyphs)
#define c_color (ctx->tsys.text_base + text_color)
#define c_fill (ctx->csys.base + canvas_fill)
#define c_updateonce (ctx->csys.base + canvas_updateonce)
#define imui_draw_group ((1 << 30) - 1)
#define imui_draw_order(x) ((x) << 16)
#define imui_ontop imui_draw_order((1 << 15) - 1)
#define imui_onbottom imui_draw_order(-(1 << 15) + 1)
typedef struct imui_state_s {
struct imui_state_s *next;
struct imui_state_s **prev;
char *label;
uint32_t label_len;
int key_offset;
imui_window_t *menu;
int32_t draw_order; // for window canvases
int32_t draw_group;
uint32_t first_link;
uint32_t num_links;
uint32_t frame_count;
uint32_t old_entity;
uint32_t entity;
uint32_t content;
view_pos_t pos;
view_pos_t len;
imui_frac_t fraction;
bool auto_fit;
} 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;
struct DARRAY_TYPE(imui_state_t *) links;
struct DARRAY_TYPE(imui_state_t *) scrollers;
int32_t draw_order;
imui_window_t *current_menu;
imui_state_t *current_state;
dstring_t *dstr;
uint32_t hot;
uint32_t active;
view_pos_t hot_position;
view_pos_t active_position;
view_pos_t mouse_active;
uint32_t mouse_pressed;
uint32_t mouse_released;
uint32_t mouse_buttons;
view_pos_t mouse_position;
uint32_t shift;
int key_code;
int unicode;
imui_style_t style;
struct DARRAY_TYPE(imui_style_t) style_stack;
};
static void
imui_reference_destroy (void *_ref, ecs_registry_t *reg)
{
imui_reference_t *ref = _ref;
if (ref->ctx) {
#if 1
//FIXME there's something wrong such that deleting the entity directly
//instead of via a view results in corrupted href componets and an
//href component leak
auto ctx = ref->ctx;
auto view = View_FromEntity (ctx->vsys, ref->ref_id);
View_Delete (view);
#else
ECS_DelEntity (ref->ctx->csys.reg, ref->ref_id);
#endif
}
}
const component_t imui_components[imui_comp_count] = {
[imui_fraction_x] = {
.size = sizeof (imui_frac_t),
.name = "fraction x",
},
[imui_fraction_y] = {
.size = sizeof (imui_frac_t),
.name = "fraction y",
},
[imui_reference] = {
.size = sizeof (imui_reference_t),
.name = "reference",
.destroy = imui_reference_destroy,
},
};
static int32_t
imui_next_window (imui_ctx_t *ctx)
{
ctx->draw_order &= ~(imui_draw_order (1) - 1);
ctx->draw_order += imui_draw_order (1);
return ctx->draw_order;
}
static imui_state_t *
imui_state_new (imui_ctx_t *ctx, uint32_t entity)
{
imui_state_t *state = PR_RESNEW (ctx->state_map);
*state = (imui_state_t) {
.next = ctx->states,
.prev = &ctx->states,
.old_entity = nullent,
.entity = entity,
};
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_find_state (imui_ctx_t *ctx, const char *label)
{
int key_offset = 0;
const char *key = strstr (label, "##");
if (key) {
// key is '###': hash only past this
if (key[2] == '#') {
key_offset = (key += 3) - label;
}
}
return Hash_Find (ctx->tab, label + key_offset);
}
static imui_state_t *
imui_get_state (imui_ctx_t *ctx, const char *label, uint32_t entity)
{
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;
}
}
auto state = imui_find_state (ctx, label);
if (state) {
state->old_entity = state->entity;
state->entity = entity;
state->frame_count = ctx->frame_count;
ctx->current_state = state;
Ent_SetComponent (entity, ecs_name, ctx->csys.reg, &state->label);
return state;
}
state = imui_state_new (ctx, entity);
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);
ctx->current_state = state;
Ent_SetComponent (entity, ecs_name, ctx->csys.reg, &state->label);
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)
{
qfZoneScoped (true);
imui_ctx_t *ctx = malloc (sizeof (imui_ctx_t));
uint32_t canvas = Canvas_New (canvas_sys);
*ctx = (imui_ctx_t) {
.csys = canvas_sys,
.canvas = canvas,
.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),
.links = DARRAY_STATIC_INIT (8),
.scrollers = 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;
}
static void
clear_items (imui_ctx_t *ctx)
{
uint32_t root_ent = ctx->root_view.id;
// delete the root view (but not the root entity)
Ent_RemoveComponent (root_ent, ctx->root_view.comp, ctx->root_view.reg);
for (uint32_t i = 0; i < ctx->windows.size; i++) {
auto window = View_FromEntity (ctx->vsys, ctx->windows.a[i]->entity);
View_Delete (window);
}
DARRAY_RESIZE (&ctx->parent_stack, 0);
DARRAY_RESIZE (&ctx->windows, 0);
DARRAY_RESIZE (&ctx->links, 0);
DARRAY_RESIZE (&ctx->scrollers, 0);
DARRAY_RESIZE (&ctx->style_stack, 0);
}
void
IMUI_DestroyContext (imui_ctx_t *ctx)
{
clear_items (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->links);
DARRAY_CLEAR (&ctx->scrollers);
DARRAY_CLEAR (&ctx->style_stack);
dstring_delete (ctx->dstr);
Hash_DelTable (ctx->tab);
Hash_DelContext (ctx->hashctx);
Shaper_Delete (ctx->shaper);
auto reg = ctx->csys.reg;
auto pool = &reg->comp_pools[c_reference];
for (uint32_t i = 0; i < pool->count; i++) {
auto ref = &((imui_reference_t *)pool->data)[i];
if (ref->ctx == ctx) {
ref->ctx = 0;
}
}
free (ctx);
}
void
IMUI_SetVisible (imui_ctx_t *ctx, bool visible)
{
if (!visible) {
ctx->active = nullent;
}
*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 });
}
bool
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 };
if (ie_event->mouse.type == ie_mousedown
|| ie_event->mouse.type == ie_mouseup) {
unsigned old = ctx->mouse_buttons;
unsigned new = m->buttons;
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;
}
return ctx->hot != nullent || ctx->active != nullent;
}
imui_io_t
IMUI_GetIO (imui_ctx_t *ctx)
{
return (imui_io_t) {
.mouse = ctx->mouse_position,
.buttons = ctx->mouse_buttons,
.pressed = ctx->mouse_pressed,
.released = ctx->mouse_released,
.hot = ctx->hot,
.active = ctx->active,
};
}
static void
set_hierarchy_tree_mode (imui_ctx_t *ctx, hierref_t ref, bool tree)
{
auto reg = ctx->csys.reg;
hierarchy_t *h = Ent_GetComponent (ref.id, ecs_hierarchy, reg);
Hierarchy_SetTreeMode (h, tree);
viewcont_t *cont = h->components[view_control];
uint32_t *ent = h->ent;
for (uint32_t i = 0; i < h->num_objects; i++) {
if (cont[i].is_link) {
imui_reference_t *sub = Ent_GetComponent (ent[i], c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
set_hierarchy_tree_mode (ctx, View_GetRef (sub_view), tree);
}
}
}
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);
clear_items (ctx);
ctx->root_view = View_AddToEntity (root_ent, ctx->vsys, nullview, true);
set_hierarchy_tree_mode (ctx, View_GetRef (ctx->root_view), 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;
ctx->draw_order = imui_draw_order (ctx->windows.size);
ctx->current_menu = 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_fraction: return ONG;
case imui_size_fitchildren: return MAG;
case imui_size_expand: return RED;
}
return BLU;
}
static void __attribute__((used))
dump_tree (hierref_t href, int level, imui_ctx_t *ctx)
{
auto reg = ctx->csys.reg;
uint32_t ind = href.index;
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, reg);
view_pos_t *abs = h->components[view_abs];
view_pos_t *len = h->components[view_len];
view_resize_f *resize = h->components[view_onresize];
view_move_f *move = h->components[view_onmove];
auto c = ((viewcont_t *)h->components[view_control])[ind];
uint32_t e = h->ent[ind];
printf ("%3d:%08x %*s[%s%d %s%d"DFL"] [%s%d %s%d"DFL"] %c%s%s %s%d %s%d"DFL,
ind, e,
level * 3, "",
view_color (h, ind, ctx, false), abs[ind].x,
view_color (h, ind, ctx, true), abs[ind].y,
view_color (h, ind, ctx, false), len[ind].x,
view_color (h, ind, ctx, true), len[ind].y,
c.vertical ? 'v' : 'h',
resize[ind] ? "R" : "",
move[ind] ? "M" : "",
view_color (h, ind, ctx, false), c.semantic_x,
view_color (h, ind, ctx, true), c.semantic_y);
for (uint32_t j = 0; j < reg->components.size; j++) {
if (Ent_HasComponent (e, j, reg)) {
printf (", %s", reg->components.a[j].name);
if (j == c_fraction_x || j == c_fraction_y) {
auto val = *(imui_frac_t *) Ent_GetComponent (e, j, reg);
printf ("(%s%d/%d"DFL")",
view_color (h, ind, ctx, j == c_fraction_y),
val.num, val.den);
}
if (j == ecs_name) {
auto name = *(const char **) Ent_GetComponent (e, j, reg);
printf ("(%s)", name);
}
if (j == c_reference) {
auto ref = *(imui_reference_t *) Ent_GetComponent (e, j, reg);
printf ("(%08x)", ref.ref_id);
}
}
}
printf (DFL"\n");
if (c.is_link) {
printf (GRN"%3d: %*slink"DFL"\n", ind, 8 + level * 3, "");
auto reg = ctx->csys.reg;
uint32_t ent = h->ent[ind];
imui_reference_t *sub = Ent_GetComponent (ent, c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
auto href = View_GetRef (sub_view);
dump_tree (href, level + 1, ctx);
printf (RED"%3d: %*slink"DFL"\n", ind, 8 + level * 3, "");
}
if (h->childIndex[ind] > ind) {
for (uint32_t i = 0; i < h->childCount[ind]; i++) {
if (h->childIndex[ind] + i >= h->num_objects) {
break;
}
hierref_t cref = { .id = href.id, .index = h->childIndex[ind] + i };
dump_tree (cref, level + 1, ctx);
}
}
if (!level) {
puts ("");
}
}
typedef struct {
bool x, y;
uint32_t num_views; // number of views in sub-hierarchy
} downdep_t;
static int
fraction (uint32_t ent, int len, uint32_t fcomp, ecs_registry_t *reg)
{
auto f = *(imui_frac_t *) Ent_GetComponent (ent, fcomp, reg);
int val = len;
if (f.den > 0) {
val = (len * f.num) / f.den;
if (val > len) {
val = len;
}
if (val < 1) {
val = 1;
}
}
return val;
}
static void
calc_upwards_dependent (imui_ctx_t *ctx, hierref_t href,
downdep_t *down_depend)
{
auto reg = ctx->csys.reg;
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, 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;
downdep_t *side_depend = down_depend + h->num_objects;
down_depend[0].num_views = h->num_objects;
for (uint32_t i = 0; i < h->num_objects; i++) {
if (i > 0) {
down_depend[i].num_views = 0;
}
if (cont[i].semantic_x == imui_size_fitchildren
|| cont[i].semantic_x == imui_size_expand) {
down_depend[i].x = true;
} else if (!(i > 0 && (down_depend[i].x = down_depend[parent[i]].x))
&& cont[i].semantic_x == imui_size_fraction) {
len[i].x = fraction (ent[i], len[parent[i]].x, c_fraction_x, reg);
} else if (cont[i].semantic_x == imui_size_pixels
&& Ent_HasComponent (ent[i], c_fraction_x, reg)) {
//FIXME uses numerator for pixels
int *pixels = Ent_GetComponent (ent[i], c_fraction_x, reg);
len[i].x = *pixels;
down_depend[i].x = false;
}
if (cont[i].semantic_y == imui_size_fitchildren
|| cont[i].semantic_y == imui_size_expand) {
down_depend[i].y = true;
} else if (!(i > 0 && (down_depend[i].y = down_depend[parent[i]].y))
&& cont[i].semantic_y == imui_size_fraction) {
len[i].y = fraction (ent[i], len[parent[i]].y, c_fraction_y, reg);
} else if (cont[i].semantic_y == imui_size_pixels
&& Ent_HasComponent (ent[i], c_fraction_y, reg)) {
//FIXME uses numerator for pixels
int *pixels = Ent_GetComponent (ent[i], c_fraction_y, reg);
len[i].y = *pixels;
down_depend[i].y = false;
}
if (cont[i].is_link) {
imui_reference_t *sub = Ent_GetComponent (ent[i], c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
auto href = View_GetRef (sub_view);
// control logic was propagated from the linked hierarcy, so
// propagate down_depend to the linked hierarcy.
side_depend[0] = down_depend[i];
calc_upwards_dependent (ctx, href, side_depend);
down_depend[0].num_views += side_depend[0].num_views;
side_depend += side_depend[0].num_views;
}
}
}
static void
calc_downwards_dependent (imui_ctx_t *ctx, hierref_t href)
{
auto reg = ctx->csys.reg;
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, 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 = h->num_objects; i-- > 0; ) {
if (cont[i].is_link) {
imui_reference_t *sub = Ent_GetComponent (ent[i], c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
calc_downwards_dependent (ctx, View_GetRef (sub_view));
len[i] = View_GetLen (sub_view);
}
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, hierref_t href)
{
auto reg = ctx->csys.reg;
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, reg);
uint32_t *ent = h->ent;
uint32_t *parent = h->parentIndex;
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++) {
if (i && cont[i].semantic_x == imui_size_fraction) {
len[i].x = fraction (ent[i], len[parent[i]].x, c_fraction_x, reg);
}
if (i && cont[i].semantic_y == imui_size_fraction) {
len[i].y = fraction (ent[i], len[parent[i]].y, c_fraction_y, reg);
}
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) {
//FIXME uses numerator for weight
int *p = Ent_GetComponent (ent[child], c_fraction_x, reg);
elen.x += *p;
ecount.x++;
}
if (cont[child].semantic_y == imui_size_expand) {
//FIXME uses numerator for weight
int *p = Ent_GetComponent (ent[child], c_fraction_y, reg);
elen.y += *p;
ecount.y++;
}
}
if (!ecount.x && !ecount.y) {
continue;
}
if (cont[i].vertical) {
int space = len[i].y - tlen.y;
int filled = 0;
for (uint32_t j = 0; j < h->childCount[i]; j++) {
uint32_t child = h->childIndex[i] + j;
if (cont[child].semantic_x == imui_size_expand) {
len[child].x = len[i].x;
}
if (cont[child].semantic_y == imui_size_expand) {
//FIXME uses numerator for weight
int *p = Ent_GetComponent (ent[child], c_fraction_y, reg);
int delta = *p * space / elen.y;
len[child].y += max (delta, 0);
filled += max (delta, 0);
}
}
if (ecount.y && filled < space) {
space -= filled;
for (uint32_t j = 0; space && j < h->childCount[i]; j++) {
uint32_t child = h->childIndex[i] + j;
if (cont[child].semantic_y == imui_size_expand) {
len[child].y++;
space--;
}
}
}
} else {
int space = len[i].x - tlen.x;
int filled = 0;
for (uint32_t j = 0; j < h->childCount[i]; j++) {
uint32_t child = h->childIndex[i] + j;
if (cont[child].semantic_x == imui_size_expand) {
//FIXME uses numerator for weight
int *p = Ent_GetComponent (ent[child], c_fraction_x, reg);
int delta = *p * space / elen.x;
len[child].x += max (delta, 0);
filled += max (delta, 0);
}
if (cont[child].semantic_y == imui_size_expand) {
len[child].y = len[i].y;
}
}
if (ecount.x && filled < space) {
space -= filled;
for (uint32_t j = 0; space && j < h->childCount[i]; j++) {
uint32_t child = h->childIndex[i] + j;
if (cont[child].semantic_x == imui_size_expand) {
len[child].x++;
space--;
}
}
}
}
}
for (uint32_t i = 0; i < h->num_objects; i++) {
if (cont[i].is_link) {
imui_reference_t *sub = Ent_GetComponent (ent[i], c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
View_SetLen (sub_view, len[i].x, len[i].y);
calc_expansions (ctx, View_GetRef (sub_view));
if (sub->update) {
View_UpdateHierarchy (sub_view);
if (cont[i].semantic_x == imui_size_fitchildren) {
len[i].x = View_GetLen (sub_view).x;
}
if (cont[i].semantic_y == imui_size_fitchildren) {
len[i].y = View_GetLen (sub_view).y;
}
}
}
}
}
static uint32_t __attribute__((pure))
count_views (imui_ctx_t *ctx, hierref_t href)
{
auto reg = ctx->csys.reg;
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, reg);
uint32_t count = h->num_objects;
viewcont_t *cont = h->components[view_control];
uint32_t *ent = h->ent;
// the root object is never a link (if it has a reference component, it's
// to the pseudo-parent of the hierarchy)
for (uint32_t i = 1; i < h->num_objects; i++) {
if (cont[i].is_link) {
imui_reference_t *sub = Ent_GetComponent (ent[i], c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
count += count_views (ctx, View_GetRef (sub_view));
}
}
return count;
}
static void
position_views (imui_ctx_t *ctx, view_t root_view)
{
auto reg = ctx->vsys.reg;
auto href = View_GetRef (root_view);
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, reg);
uint32_t *ent = h->ent;
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;
if (Ent_HasComponent (root_view.id, c_reference, ctx->vsys.reg)) {
auto ent = root_view.id;
imui_reference_t *reference = Ent_GetComponent (ent, c_reference, reg);
auto anchor = View_FromEntity (ctx->vsys, reference->ref_id);
pos[0] = View_GetAbs (anchor);
}
view_pos_t cpos = {};
uint32_t cur_parent = 0;
for (uint32_t i = 0; 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 (i > 0 && 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);
for (uint32_t i = 0; i < h->num_objects; i++) {
if (cont[i].is_link) {
auto reg = ctx->vsys.reg;
imui_reference_t *sub = Ent_GetComponent (ent[i], c_reference, reg);
auto sub_view = View_FromEntity (ctx->vsys, sub->ref_id);
position_views (ctx, sub_view);
}
}
}
//FIXME currently works properly only for grav_northwest
static void
layout_objects (imui_ctx_t *ctx, view_t root_view)
{
auto href = View_GetRef (root_view);
downdep_t down_depend[count_views (ctx, href)];
// the root view size is always explicit
down_depend[0] = (downdep_t) { };
calc_upwards_dependent (ctx, href, down_depend);
calc_downwards_dependent (ctx, href);
calc_expansions (ctx, href);
//dump_tree (href, 0, ctx);
// resolve conflicts
//fflush (stdout);
position_views (ctx, root_view);
//dump_tree (href, 0, ctx);
}
static void
check_inside (imui_ctx_t *ctx, view_t root_view)
{
auto reg = ctx->vsys.reg;
auto href = View_GetRef (root_view);
hierarchy_t *h = Ent_GetComponent (href.id, ecs_hierarchy, reg);
uint32_t *ent = 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 == ent[i] || ctx->active == nullent) {
ctx->hot = ent[i];
ctx->hot_position = abs[i];
}
}
if (ent[i] == ctx->active) {
ctx->active_position = abs[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 = imui_draw_order (i + 1);
*Canvas_DrawOrder (ctx->csys, window->entity) = window->draw_order;
for (uint32_t j = 0; j < window->num_links; j++) {
auto link = ctx->links.a[window->first_link + j];
link->draw_order = window->draw_order + j + 1;
*Canvas_DrawOrder (ctx->csys, link->entity) = link->draw_order;
}
}
}
void
IMUI_Draw (imui_ctx_t *ctx)
{
ctx->frame_draw = Sys_LongTime ();
ctx->mouse_pressed = 0;
ctx->mouse_released = 0;
sort_windows (ctx);
auto href = View_GetRef (ctx->root_view);
set_hierarchy_tree_mode (ctx, href, false);
for (uint32_t i = 0; i < ctx->windows.size; i++) {
auto win = ctx->windows.a[i];
auto window = View_FromEntity (ctx->vsys, win->entity);
View_SetPos (window, win->pos.x, win->pos.y);
if (win->auto_fit) {
View_SetLen (window, 0, 0);
} else {
View_SetLen (window, win->len.x, win->len.y);
}
set_hierarchy_tree_mode (ctx, View_GetRef (window), 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);
}
for (uint32_t i = 0; i < ctx->scrollers.size; i++) {
auto scroller = ctx->scrollers.a[i];
auto view = View_FromEntity (ctx->vsys, scroller->entity);
scroller->len = View_GetLen (view);
}
ctx->frame_end = Sys_LongTime ();
}
int
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) {
Ent_SetComponent (view.id, c_fraction_x, ctx->csys.reg,
&(imui_frac_t) { 100, 100 });
}
if (y_size == imui_size_expand) {
Ent_SetComponent (view.id, c_fraction_y, ctx->csys.reg,
&(imui_frac_t) { 100, 100 });
}
return 0;
}
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_fraction || size == imui_size_expand
|| size == imui_size_pixels) {
Ent_SetComponent (id, c_fraction_x, ctx->csys.reg,
&(imui_frac_t) { value, 100 });
}
}
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_fraction || size == imui_size_expand
|| size == imui_size_pixels) {
Ent_SetComponent (id, c_fraction_y, ctx->csys.reg,
&(imui_frac_t) { value, 100 });
}
}
int
IMUI_PushStyle (imui_ctx_t *ctx, const imui_style_t *style)
{
DARRAY_APPEND (&ctx->style_stack, ctx->style);
if (style) {
ctx->style = *style;
}
return 0;
}
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 & 1) {
result = ctx->hot == entity;
ctx->active = nullent;
}
} else if (ctx->hot == entity) {
if (ctx->mouse_pressed & 1) {
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) {
auto active = ctx->active_position;
auto anchor = VP_add (active, ctx->mouse_active);
delta = VP_sub (ctx->mouse_position, anchor);
if (ctx->mouse_released & 1) {
ctx->active = nullent;
}
} else if (ctx->hot == entity) {
if (ctx->mouse_pressed & 1) {
ctx->mouse_active = VP_sub (ctx->mouse_position, ctx->hot_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);
// prevent the layout system from repositioning the text view
View_Control (text)->free_x = 1;
View_Control (text)->free_y = 1;
View_SetVisible (text, 1);
Ent_SetComponent (text.id, c_glyphs, reg,
Ent_GetComponent (text.id, t_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, imui_state_t *state)
{
int mode = 0;
if (state->old_entity != nullent) {
if (ctx->hot == state->old_entity) {
ctx->hot = state->entity;
mode = 1;
}
if (ctx->active == state->old_entity) {
ctx->active = state->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;
Ent_SetComponent (view.id, c_fraction_x, ctx->csys.reg,
&(imui_frac_t) { weight, 100 });
}
void
IMUI_Label (imui_ctx_t *ctx, const char *label)
{
auto view = View_New (ctx->vsys, ctx->current_parent);
set_control (ctx, view, true);
auto state = imui_get_state (ctx, label, view.id);
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);
}
void
IMUI_Passage (imui_ctx_t *ctx, const char *name, struct passage_s *passage)
{
auto anchor_view = View_New (ctx->vsys, ctx->current_parent);
*View_Control (anchor_view) = (viewcont_t) {
.gravity = grav_northwest,
.visible = 1,
.semantic_x = imui_size_expand,
.semantic_y = imui_size_fitchildren,
.free_y = 1,
.vertical = true,
.active = 1,
};
auto reg = ctx->csys.reg;
Ent_SetComponent (anchor_view.id, c_fraction_x, reg,
&(imui_frac_t) { 100, 100 });
uint32_t parent = ctx->current_parent.id;
if (Ent_HasComponent (parent, ecs_name, ctx->csys.reg)) {
name = *(char **) Ent_GetComponent (parent, ecs_name, ctx->csys.reg);
}
auto state = imui_get_state (ctx, va (0, "%s#content", name),
anchor_view.id);
DARRAY_APPEND (&ctx->scrollers, state);
update_hot_active (ctx, state);
View_SetPos (anchor_view, -state->pos.x, -state->pos.y);
set_fill (ctx, anchor_view, ctx->style.background.normal);
auto psg_view = Text_PassageView (ctx->tsys, nullview,
ctx->font, passage, ctx->shaper);
Canvas_SetReference (ctx->csys, psg_view.id,
Canvas_Entity (ctx->csys,
View_GetRoot (anchor_view).id));
// FIXME this shouldn't be necessary and is a sign of bigger problems
Ent_RemoveComponent (psg_view.id, c_passage_glyphs, reg);
Ent_SetComponent (psg_view.id, c_passage_glyphs, reg,
Ent_GetComponent (psg_view.id, t_passage_glyphs, reg));
*View_Control (psg_view) = (viewcont_t) {
.gravity = grav_northwest,
.visible = 1,
.free_x = 1,
.free_y = 1,
.vertical = true,
.active = 1,
};
View_Control (anchor_view)->is_link = 1;
imui_reference_t link = {
.ref_id = psg_view.id,
.update = true,
};
Ent_SetComponent (anchor_view.id, c_reference, anchor_view.reg, &link);
imui_reference_t anchor = {
.ref_id = anchor_view.id,
};
Ent_SetComponent (psg_view.id, c_reference, psg_view.reg, &anchor);
}
bool
IMUI_Button (imui_ctx_t *ctx, const char *label)
{
auto view = View_New (ctx->vsys, ctx->current_parent);
set_control (ctx, view, true);
auto state = imui_get_state (ctx, label, view.id);
int mode = update_hot_active (ctx, state);
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 view = View_New (ctx->vsys, ctx->current_parent);
set_control (ctx, view, true);
View_Control (view)->semantic_x = imui_size_fitchildren;
View_Control (view)->semantic_y = imui_size_fitchildren;
auto state = imui_get_state (ctx, label, view.id);
int mode = update_hot_active (ctx, state);
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]);
}
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)) {
*flag = !*flag;
}
return *flag;
}
void
IMUI_Radio (imui_ctx_t *ctx, int *curvalue, int value, const char *label)
{
auto view = View_New (ctx->vsys, ctx->current_parent);
set_control (ctx, view, true);
View_Control (view)->semantic_x = imui_size_fitchildren;
View_Control (view)->semantic_y = imui_size_fitchildren;
auto state = imui_get_state (ctx, label, view.id);
int mode = update_hot_active (ctx, state);
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)
{
}
static view_t
sized_view (imui_ctx_t *ctx,
imui_size_t xsize, int xvalue,
imui_size_t ysize, int yvalue, bool active)
{
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, active);
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_fraction || xsize == imui_size_expand) {
Ent_SetComponent (view.id, c_fraction_x, reg,
&(imui_frac_t) { xvalue, 100 });
}
if (ysize == imui_size_fraction || ysize == imui_size_expand) {
Ent_SetComponent (view.id, c_fraction_y, reg,
&(imui_frac_t) { yvalue, 100 });
}
return view;
}
void
IMUI_Spacer (imui_ctx_t *ctx,
imui_size_t xsize, int xvalue,
imui_size_t ysize, int yvalue)
{
auto view = sized_view (ctx, xsize, xvalue, ysize, yvalue, false);
set_fill (ctx, view, ctx->style.background.normal);
}
static void
create_reference_anchor (imui_ctx_t *ctx, uint32_t ent, imui_window_t *panel)
{
auto state = imui_find_state (ctx, panel->reference);
if (!state) {
Sys_Printf ("IMUI: unknown widget '%s' for '%s'\n", panel->reference,
panel->name);
return;
}
if (!ECS_EntValid (state->entity, ctx->csys.reg)) {
Sys_Printf ("IMUI: invalid widget reference '%s' for '%s'\n",
panel->reference, panel->name);
return;
}
auto refview = View_FromEntity (ctx->vsys, state->entity);
auto anchor = View_New (ctx->vsys, refview);
View_SetPos (anchor, 0, 0);
View_SetLen (anchor, 0, 0);
View_SetGravity (anchor, panel->anchor_gravity);
imui_reference_t reference = {
.ref_id = anchor.id,
};
Ent_SetComponent (ent, c_reference, ctx->vsys.reg, &reference);
}
view_pos_t
IMUI_Dragable (imui_ctx_t *ctx,
imui_size_t xsize, int xvalue,
imui_size_t ysize, int yvalue,
const char *name)
{
auto view = sized_view (ctx, xsize, xvalue, ysize, yvalue, true);
auto state = imui_get_state (ctx, name, view.id);
int mode = update_hot_active (ctx, state);
auto delta = check_drag_delta (ctx, state->entity);
set_fill (ctx, view, ctx->style.foreground.color[mode]);
return delta;
}
static void
drag_window_tl (view_pos_t delta, imui_window_t *window)
{
int x = max (window->xlen - delta.x, 20);
int y = max (window->ylen - delta.y, 20);
window->xpos += window->xlen - x;
window->ypos += window->ylen - y;
window->xlen = x;
window->ylen = y;
}
static void
drag_window_tc (view_pos_t delta, imui_window_t *window)
{
int y = max (window->ylen - delta.y, 20);
window->ypos += window->ylen - y;
window->ylen = y;
}
static void
drag_window_tr (view_pos_t delta, imui_window_t *window)
{
int x = max (window->xlen + delta.x, 20);
int y = max (window->ylen - delta.y, 20);
window->ypos += window->ylen - y;
window->xlen = x;
window->ylen = y;
}
static void
drag_window_cl (view_pos_t delta, imui_window_t *window)
{
int x = max (window->xlen - delta.x, 20);
window->xpos += window->xlen - x;
window->xlen = x;
}
static void
drag_window_cr (view_pos_t delta, imui_window_t *window)
{
int x = max (window->xlen + delta.x, 20);
window->xlen = x;
}
static void
drag_window_bl (view_pos_t delta, imui_window_t *window)
{
int x = max (window->xlen - delta.x, 20);
int y = max (window->ylen + delta.y, 20);
window->xpos += window->xlen - x;
window->xlen = x;
window->ylen = y;
}
static void
drag_window_bc (view_pos_t delta, imui_window_t *window)
{
int y = max (window->ylen + delta.y, 20);
window->ylen = y;
}
static void
drag_window_br (view_pos_t delta, imui_window_t *window)
{
int x = max (window->xlen + delta.x, 20);
int y = max (window->ylen + delta.y, 20);
window->xlen = x;
window->ylen = y;
}
#define drag(c,e,sx,xv,sy,yv,n,p) \
drag_window_##c (IMUI_Dragable (ctx, sx, xv, sy, yv, \
va (0, "%s##drag_" #c #e, n)), p);
int
IMUI_StartPanel (imui_ctx_t *ctx, imui_window_t *panel)
{
if (!panel->is_open) {
return 1;
}
auto canvas = Canvas_New (ctx->csys);
int draw_group = imui_draw_group + panel->group_offset;
*Canvas_DrawGroup (ctx->csys, canvas) = draw_group;
auto panel_view = Canvas_GetRootView (ctx->csys, canvas);
auto state = imui_get_state (ctx, panel->name, panel_view.id);
state->draw_group = draw_group;
panel->mode = update_hot_active (ctx, state);
DARRAY_APPEND (&ctx->parent_stack, ctx->current_parent);
set_hierarchy_tree_mode (ctx, View_GetRef (panel_view), true);
grav_t gravity = grav_northwest;
if (panel->reference) {
create_reference_anchor (ctx, canvas, panel);
gravity = panel->reference_gravity;
}
state->first_link = ctx->links.size;
state->num_links = 0;
DARRAY_APPEND (&ctx->windows, state);
if (!state->draw_order) {
state->draw_order = imui_next_window (ctx);
}
auto semantic = panel->auto_fit ? imui_size_fitchildren : imui_size_pixels;
ctx->current_parent = panel_view;
*View_Control (panel_view) = (viewcont_t) {
.gravity = gravity,
.visible = 1,
.semantic_x = semantic,
.semantic_y = semantic,
.free_x = 1,
.free_y = 1,
.vertical = true,
.active = 1,
};
auto bg = ctx->style.background.normal;
UI_Vertical {
ctx->style.background.normal = 0;
if (!panel->auto_fit) {
IMUI_Layout_SetYSize (ctx, imui_size_expand, 100);
UI_Horizontal {
drag (tl,t, imui_size_pixels, 12,
imui_size_pixels, 4, panel->name, panel);
drag (tc, , imui_size_expand, 100,
imui_size_pixels, 4, panel->name, panel);
drag (tr,t, imui_size_pixels, 12,
imui_size_pixels, 4, panel->name, panel);
}
} else {
IMUI_Spacer (ctx, imui_size_expand, 100, imui_size_pixels, 2);
}
UI_Horizontal {
if (!panel->auto_fit) {
IMUI_Layout_SetYSize (ctx, imui_size_expand, 100);
UI_Vertical {
drag (tl,s, imui_size_pixels, 4,
imui_size_pixels, 8, panel->name, panel);
drag (cl, , imui_size_pixels, 4,
imui_size_expand, 100, panel->name, panel);
drag (bl,s, imui_size_pixels, 4,
imui_size_pixels, 8, panel->name, panel);
}
} else {
IMUI_Spacer (ctx, imui_size_pixels, 2, imui_size_expand, 100);
}
UI_Vertical {
IMUI_Layout_SetXSize (ctx, imui_size_expand, 100);
panel_view = ctx->current_parent;
}
if (!panel->auto_fit) {
UI_Vertical {
drag (tr,s, imui_size_pixels, 4,
imui_size_pixels, 8, panel->name, panel);
drag (cr, , imui_size_pixels, 4,
imui_size_expand, 100, panel->name, panel);
drag (br,s, imui_size_pixels, 4,
imui_size_pixels, 8, panel->name, panel);
}
} else {
IMUI_Spacer (ctx, imui_size_pixels, 2, imui_size_expand, 100);
}
}
if (!panel->auto_fit) {
UI_Horizontal {
drag (bl,b, imui_size_pixels, 12,
imui_size_pixels, 4, panel->name, panel);
drag (bc, , imui_size_expand, 100,
imui_size_pixels, 4, panel->name, panel);
drag (br,b, imui_size_pixels, 12,
imui_size_pixels, 4, panel->name, panel);
}
} else {
IMUI_Spacer (ctx, imui_size_expand, 100, imui_size_pixels, 2);
}
}
state->pos = (view_pos_t) {
.x = panel->xpos,
.y = panel->ypos,
};
state->len = (view_pos_t) {
.x = panel->xlen,
.y = panel->ylen,
};
state->auto_fit = panel->auto_fit;
ctx->style.background.normal = bg;
ctx->current_parent = panel_view;
state->content = panel_view.id;
return 0;
}
int
IMUI_ExtendPanel (imui_ctx_t *ctx, const char *panel_name)
{
auto state = imui_find_state (ctx, panel_name);
if (!state || !ECS_EntValid (state->entity, ctx->vsys.reg)) {
return 1;
}
DARRAY_APPEND (&ctx->parent_stack, ctx->current_parent);
ctx->current_parent = View_FromEntity (ctx->vsys, state->content);
return 0;
}
void
IMUI_EndPanel (imui_ctx_t *ctx)
{
IMUI_PopLayout (ctx);
}
int
IMUI_StartMenu (imui_ctx_t *ctx, imui_window_t *menu, bool vertical)
{
menu->parent = ctx->current_menu;
ctx->current_menu = menu;
if (menu->parent) {
if (!menu->reference) {
menu->reference = nva ("%s##item:%s", menu->name,
menu->parent->name);
}
if (menu->parent->is_open) {
UI_ExtendPanel (menu->parent->name) {
if (IMUI_MenuItem (ctx, menu->reference, false)) {
menu->is_open = true;
}
}
}
}
if (!menu->is_open) {
ctx->current_menu = menu->parent;
return 1;
}
IMUI_StartPanel (ctx, menu);
auto state = ctx->windows.a[ctx->windows.size - 1];
state->menu = menu;
auto menu_view = ctx->current_parent;
if (vertical) {
IMUI_Layout_SetXSize (ctx, imui_size_fitchildren, 0);
} else {
UI_Horizontal {
IMUI_Layout_SetYSize (ctx, imui_size_fitchildren, 0);
menu_view = ctx->current_parent;
}
}
ctx->current_parent = menu_view;
state->content = menu_view.id;
return 0;
}
void
IMUI_EndMenu (imui_ctx_t *ctx)
{
IMUI_PopLayout (ctx);
}
bool
IMUI_MenuItem (imui_ctx_t *ctx, const char *label, bool collapse)
{
auto res = IMUI_Button (ctx, label);
//auto state = ctx->current_state;
if (res && collapse) {
for (auto m = ctx->current_menu; m && !m->no_collapse; m = m->parent) {
m->is_open = false;
}
}
return res;
}
void
IMUI_TitleBar (imui_ctx_t *ctx, imui_window_t *window)
{
auto state = ctx->windows.a[ctx->windows.size - 1];
auto title_bar = View_New (ctx->vsys, ctx->current_parent);
auto tb_state = imui_get_state (ctx, va (0, "%s##title_bar", window->name),
title_bar.id);
int tb_mode = update_hot_active (ctx, tb_state);
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, window->mode);
View_Control (title)->gravity = grav_center;
}
void
IMUI_CollapseButton (imui_ctx_t *ctx, imui_window_t *window)
{
char cbutton = window->is_collapsed ? '>' : 'v';
if (UI_Button (va (0, "%c##collapse_%s", cbutton, window->name))) {
window->is_collapsed = !window->is_collapsed;
}
}
void
IMUI_CloseButton (imui_ctx_t *ctx, imui_window_t *window)
{
if (UI_Button (va (0, "X##close_%s", window->name))) {
window->is_open = false;
}
}
int
IMUI_StartWindow (imui_ctx_t *ctx, imui_window_t *window)
{
if (!window->is_open) {
return 1;
}
IMUI_StartPanel (ctx, window);
auto state = ctx->windows.a[ctx->windows.size - 1];
UI_Horizontal {
IMUI_CollapseButton (ctx, window);
IMUI_TitleBar (ctx, window);
IMUI_CloseButton (ctx, window);
}
state->pos = (view_pos_t) {
.x = window->xpos,
.y = window->ypos,
};
return 0;
}
void
IMUI_EndWindow (imui_ctx_t *ctx)
{
IMUI_PopLayout (ctx);
}
int
IMUI_StartScrollBox (imui_ctx_t *ctx, const char *name)
{
auto anchor_view = View_New (ctx->vsys, ctx->current_parent);
*View_Control (anchor_view) = (viewcont_t) {
.gravity = grav_northwest,
.visible = 1,
.semantic_x = imui_size_expand,
.semantic_y = imui_size_expand,
.active = 0,
};
Ent_SetComponent (anchor_view.id, c_fraction_x, ctx->csys.reg,
&(imui_frac_t) { 100, 100 });
Ent_SetComponent (anchor_view.id, c_fraction_y, ctx->csys.reg,
&(imui_frac_t) { 100, 100 });
auto panel = ctx->windows.a[ctx->windows.size - 1];
auto canvas = Canvas_New (ctx->csys);
*Canvas_DrawGroup (ctx->csys, canvas) = panel->draw_group;
auto scroll_box = Canvas_GetRootView (ctx->csys, canvas);
auto state = imui_get_state (ctx, name, scroll_box.id);
update_hot_active (ctx, state);
DARRAY_APPEND (&ctx->links, state);
panel->num_links++;
DARRAY_APPEND (&ctx->scrollers, state);
DARRAY_APPEND (&ctx->parent_stack, ctx->current_parent);
set_hierarchy_tree_mode (ctx, View_GetRef (scroll_box), true);
View_Control (anchor_view)->is_link = 1;
imui_reference_t link = {
.ref_id = scroll_box.id,
.ctx = ctx,
};
Ent_SetComponent (anchor_view.id, c_reference, anchor_view.reg, &link);
imui_reference_t anchor = {
.ref_id = anchor_view.id,
};
Ent_SetComponent (scroll_box.id, c_reference, scroll_box.reg, &anchor);
if (!state->draw_order) {
state->draw_order = ++ctx->draw_order;
}
ctx->current_parent = scroll_box;
*View_Control (scroll_box) = (viewcont_t) {
.gravity = grav_northwest,
.visible = 1,
.semantic_x = imui_size_pixels,
.semantic_y = imui_size_pixels,
.free_x = 1,
.free_y = 1,
.vertical = true,
.active = 1,
};
state->content = scroll_box.id;
return 0;
}
void
IMUI_EndScrollBox (imui_ctx_t *ctx)
{
IMUI_PopLayout (ctx);
}
void
IMUI_ScrollBar (imui_ctx_t *ctx, const char *name)
{
auto pcont = View_Control (ctx->current_parent);
// if the current layout is vertical, then the scroll bar will be placed
// under the previous item (or above the next) and thus should be
// horizontal, but if the layout is horizontal, then the scroll bar will
// be paced to the side of the next/previous item and thus should be
// vertical
bool vertical = !pcont->vertical;
IMUI_PushLayout (ctx, vertical);
auto sb_view = ctx->current_parent;
if (vertical) {
IMUI_Layout_SetXSize (ctx, imui_size_pixels, 12);
} else {
IMUI_Layout_SetYSize (ctx, imui_size_pixels, 12);
}
View_Control (sb_view)->active = true;
auto tt_view = View_New (ctx->vsys, sb_view);
*View_Control (tt_view) = (viewcont_t) {
.gravity = grav_northwest,
.visible = 1,
.semantic_x = vertical ? imui_size_expand : imui_size_fraction,
.semantic_y = vertical ? imui_size_fraction : imui_size_expand,
.free_x = !vertical,
.free_y = vertical,
.active = true,
};
IMUI_PopLayout (ctx);
auto sb_state = imui_get_state (ctx, va (0, "scrollbar#%s", name),
sb_view.id);
auto tt_state = imui_get_state (ctx, va (0, "thumbtab#%s", name),
tt_view.id);
int sb_mode = update_hot_active (ctx, sb_state);
int tt_mode = update_hot_active (ctx, tt_state);
set_fill (ctx, sb_view, ctx->style.background.color[sb_mode]);
set_fill (ctx, tt_view, ctx->style.foreground.color[tt_mode]);
auto scroller = imui_find_state (ctx, name);
if (scroller) {
auto slen = scroller->len;
tt_state->fraction.num = vertical ? slen.y : slen.x;
auto content = imui_find_state (ctx, va (0, "%s#content", name));
if (content) {
auto delta = check_drag_delta (ctx, tt_state->entity);
auto clen = content->len;
if (vertical) {
content->pos.y += delta.y;
int max = clen.y - slen.y;
content->pos.y = bound (0, content->pos.y, max);
} else {
content->pos.x += delta.x;
int max = clen.x - slen.x;
content->pos.x = bound (0, content->pos.x, max);
}
auto cpos = content->pos;
tt_state->fraction.den = vertical ? clen.y : clen.x;
tt_state->pos = (view_pos_t) {0, 0};
if (vertical) {
if (clen.y) {
tt_state->pos.y = cpos.y * slen.y / clen.y;
}
} else {
if (clen.x) {
tt_state->pos.x = cpos.x * slen.x / clen.x;
}
}
}
}
View_SetPos (tt_view, tt_state->pos.x, tt_state->pos.y);
imui_frac_t efrac = { 100, 100 };
imui_frac_t tfrac = tt_state->fraction;
Ent_SetComponent (tt_view.id, c_fraction_x, ctx->csys.reg,
vertical ? &efrac : &tfrac);
Ent_SetComponent (tt_view.id, c_fraction_y, ctx->csys.reg,
vertical ? &tfrac : &efrac);
}