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.
1438 lines
39 KiB
1438 lines
39 KiB
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
#include <math.h>
|
|
|
|
#include "ugui.h"
|
|
#include "def_style.h"
|
|
|
|
|
|
#define SALT 0xbabb0cac
|
|
#define DEF_SCALE 1.0
|
|
#define DEF_PPI 96.0
|
|
#define STACK_STEP 64
|
|
#define SLICE_STEP 128
|
|
|
|
#define RESIZEALL ( \
|
|
UG_CNT_RESIZE_RIGHT | \
|
|
UG_CNT_RESIZE_BOTTOM | \
|
|
UG_CNT_RESIZE_LEFT | \
|
|
UG_CNT_RESIZE_TOP \
|
|
)
|
|
|
|
#define BTN_ANY ( \
|
|
UG_BTN_LEFT | \
|
|
UG_BTN_RIGHT | \
|
|
UG_BTN_MIDDLE | \
|
|
UG_BTN_4 | \
|
|
UG_BTN_5 \
|
|
)
|
|
|
|
#define CNT_STATE_ALL ( \
|
|
CNT_STATE_NONE | \
|
|
CNT_STATE_MOVING | \
|
|
CNT_STATE_RESIZE_T | \
|
|
CNT_STATE_RESIZE_B | \
|
|
CNT_STATE_RESIZE_L | \
|
|
CNT_STATE_RESIZE_R | \
|
|
CNT_STATE_RESIZE_D \
|
|
)
|
|
|
|
#define PPI_PPM(ppi, scale) (ppi * scale * 0.03937008)
|
|
#define PPI_PPD(ppi, scale) (PPI_PPM(ppi, scale) * 0.3528)
|
|
#define IS_VALID_UNIT(u) (u==UG_UNIT_PX||u==UG_UNIT_MM||u==UG_UNIT_PT)
|
|
#define UG_ERR(...) err(errno, "__FUNCTION__: " __VA_ARGS__)
|
|
#define BETWEEN(x, min, max) (x <= max && x >= min)
|
|
#define INTERSECTS(v, r) (BETWEEN(v.x, r.x, r.x+r.w) && BETWEEN(v.y, r.y, r.y+r.h))
|
|
#define TEST(f, b) (f & (b))
|
|
#define MAX(a, b) (a > b ? a : b)
|
|
#define R_MARGIN(r, b) (r.x + r.w - b)
|
|
#define L_MARGIN(r, b) (r.x + b)
|
|
#define T_MARGIN(r, b) (r.y + b)
|
|
#define B_MARGIN(r, b) (r.y + r.h - b)
|
|
// check if ra is outside of rb
|
|
#define OUTSIDE(ra, ba, rb, bb) (L_MARGIN(ra, ba) > R_MARGIN(rb, bb) || \
|
|
T_MARGIN(ra, ba) > B_MARGIN(rb, bb) || \
|
|
R_MARGIN(ra, ba) < L_MARGIN(rb, bb) || \
|
|
B_MARGIN(ra, ba) < T_MARGIN(rb, bb))
|
|
|
|
|
|
static ug_style_t style_cache = {0};
|
|
|
|
|
|
/*=============================================================================*
|
|
* Common Functions *
|
|
*=============================================================================*/
|
|
|
|
|
|
// grow a stack
|
|
#define GROW_STACK(S) \
|
|
{ \
|
|
S.items = realloc(S.items, (S.size+STACK_STEP)*sizeof(*(S.items))); \
|
|
if(!S.items) \
|
|
UG_ERR("Could not allocate stack #S: %s", strerror(errno)); \
|
|
memset(&(S.items[S.size]), 0, STACK_STEP*sizeof(*(S.items))); \
|
|
S.size += STACK_STEP; \
|
|
}
|
|
|
|
|
|
#define GET_FROM_STACK(S, c) \
|
|
{ \
|
|
if (S.idx >= S.size) \
|
|
GROW_STACK(S); \
|
|
c = &(S.items[S.idx++]); \
|
|
}
|
|
|
|
|
|
#define DELETE_FROM_STACK(S, c) \
|
|
{ \
|
|
for (int i = 0; i < S.idx; i++) { \
|
|
if (c->id != S.items[i].id) \
|
|
continue; \
|
|
memmove(&S.items[i], &S.items[i+1], (S.idx-i)*sizeof(S.items[0])); \
|
|
memset(&S.items[S.idx--], 0, sizeof(S.items[0])); \
|
|
} \
|
|
}
|
|
|
|
|
|
#define RESET_STACK(S) \
|
|
{ \
|
|
memset(S.items, 0, S.idx*sizeof(*(S.items))); \
|
|
S.idx = 0; \
|
|
}
|
|
|
|
|
|
#define MOUSEDOWN(ctx, btn) (~(ctx->mouse.hold & btn) & (ctx->mouse.update & btn))
|
|
#define MOUSEUP(ctx, btn) ((ctx->mouse.hold & btn) & (ctx->mouse.update & btn))
|
|
#define MOUSEHELD(ctx, btn) (ctx->mouse.hold & btn)
|
|
|
|
|
|
// https://en.wikipedia.org/wiki/Jenkins_hash_function
|
|
static ug_id_t hash(const void *data, unsigned int size)
|
|
{
|
|
if (!size)
|
|
return 0;
|
|
ug_id_t hash = SALT;
|
|
unsigned char *v = (unsigned char *)data;
|
|
|
|
for (; size; size--) {
|
|
hash += v[size-1];
|
|
hash += hash << 10;
|
|
hash ^= hash >> 6;
|
|
}
|
|
hash += hash << 3;
|
|
hash ^= hash >> 11;
|
|
hash += hash << 15;
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
int size_to_px(ug_ctx_t *ctx, ug_size_t s)
|
|
{
|
|
switch (s.unit) {
|
|
case UG_UNIT_MM: return roundf(s.size.f * ctx->ppm);
|
|
case UG_UNIT_PT: return roundf(s.size.f * ctx->ppd);
|
|
case UG_UNIT_PX: return s.size.i;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
ug_rect_t div_to_rect(ug_ctx_t *ctx, ug_div_t *div)
|
|
{
|
|
ug_rect_t r;
|
|
r.x = size_to_px(ctx, div->x);
|
|
r.y = size_to_px(ctx, div->y);
|
|
r.w = size_to_px(ctx, div->w);
|
|
r.h = size_to_px(ctx, div->h);
|
|
return r;
|
|
}
|
|
|
|
|
|
// update the style cache with the correct sizes in pixels and colors
|
|
static void update_style_cache(ug_ctx_t *ctx)
|
|
{
|
|
const ug_style_t *s = ctx->style;
|
|
// FIME: use the correct units and convert, for now assume default style
|
|
style_cache = *s;
|
|
}
|
|
|
|
|
|
void scale_rect(ug_ctx_t *ctx, ug_rect_t *rect)
|
|
{
|
|
if (ctx->ppi != ctx->last_ppi) {
|
|
// if the scale has been updated than we need to scale the container
|
|
// as well
|
|
float scale = ctx->ppi / ctx->last_ppi;
|
|
rect->x = roundf(rect->x * scale);
|
|
rect->y = roundf(rect->y * scale);
|
|
rect->w = roundf(rect->w * scale);
|
|
rect->h = roundf(rect->h * scale);
|
|
}
|
|
}
|
|
|
|
|
|
int crop_rect(ug_rect_t *ra, ug_rect_t *rb)
|
|
{
|
|
int cropped = 0;
|
|
if (L_MARGIN((*ra), 0) < L_MARGIN((*rb), 0)) {
|
|
ra->w -= L_MARGIN((*rb), 0) - L_MARGIN((*ra), 0);
|
|
ra->x = rb->x;
|
|
cropped = 1;
|
|
}
|
|
if (R_MARGIN((*ra), 0) > R_MARGIN((*rb), 0)) {
|
|
ra->w -= R_MARGIN((*ra), 0) - R_MARGIN((*rb), 0);
|
|
cropped = 1;
|
|
}
|
|
if (T_MARGIN((*ra), 0) < T_MARGIN((*rb), 0)) {
|
|
ra->h -= T_MARGIN((*rb), 0) - T_MARGIN((*ra), 0);
|
|
ra->y = rb->y;
|
|
cropped = 1;
|
|
}
|
|
if (B_MARGIN((*ra), 0) > B_MARGIN((*rb), 0)) {
|
|
ra->h -= B_MARGIN((*ra), 0)- B_MARGIN((*rb), 0);
|
|
cropped = 1;
|
|
}
|
|
return cropped;
|
|
}
|
|
|
|
|
|
/*=============================================================================*
|
|
* Command Operations *
|
|
*=============================================================================*/
|
|
|
|
|
|
static void push_rect_command(ug_ctx_t *ctx, const ug_rect_t *rect, ug_color_t color)
|
|
{
|
|
ug_cmd_t *c;
|
|
GET_FROM_STACK(ctx->cmd_stack, c);
|
|
c->type = UG_CMD_RECT;
|
|
c->rect.x = rect->x;
|
|
c->rect.y = rect->y;
|
|
c->rect.w = rect->w;
|
|
c->rect.h = rect->h;
|
|
c->rect.color = color;
|
|
}
|
|
|
|
|
|
// pushes a text command to the render command stack, str is a pointer to a valid
|
|
// string offered by the user, it must be valid at least until rendering is done
|
|
static void push_text_command(ug_ctx_t *ctx, ug_vec2_t pos, int size, ug_color_t color, const char *str)
|
|
{
|
|
ug_cmd_t *c;
|
|
GET_FROM_STACK(ctx->cmd_stack, c);
|
|
c->type = UG_CMD_TEXT;
|
|
c->text.x = pos.x;
|
|
c->text.y = pos.y;
|
|
c->text.size = size;
|
|
c->text.color = color;
|
|
c->text.str = str;
|
|
}
|
|
|
|
|
|
ug_cmd_t *ug_cmd_next(ug_ctx_t *ctx)
|
|
{
|
|
if(!ctx)
|
|
return NULL;
|
|
if (ctx->cmd_it < ctx->cmd_stack.idx)
|
|
return &ctx->cmd_stack.items[ctx->cmd_it++];
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*=============================================================================*
|
|
* Context Operations *
|
|
*=============================================================================*/
|
|
|
|
|
|
// creates a new context, fills with default values, ctx is ready for ug_start()
|
|
ug_ctx_t *ug_ctx_new(void)
|
|
{
|
|
ug_ctx_t *ctx = malloc(sizeof(ug_ctx_t));
|
|
if (!ctx)
|
|
err(errno, "__FUNCTION__:" "Could not allocate context: %s", strerror(errno));
|
|
memset(ctx, 0, sizeof(ug_ctx_t));
|
|
|
|
ctx->style = &default_style;
|
|
ctx->style_px = &style_cache;
|
|
// NOTE: this fixes a bug where for the first frame the container stack
|
|
// doesn't get sorted, but it is kind of a botch
|
|
ctx->last_active_cnt = 1;
|
|
ug_ctx_set_displayinfo(ctx, DEF_SCALE, DEF_PPI);
|
|
|
|
// TODO: allocate stacks
|
|
return ctx;
|
|
}
|
|
|
|
|
|
void ug_ctx_free(ug_ctx_t *ctx)
|
|
{
|
|
if (!ctx) {
|
|
warn("__FUNCTION__:" "Trying to free a null context");
|
|
return;
|
|
}
|
|
|
|
free(ctx->cmd_stack.items);
|
|
free(ctx->cnt_stack.items);
|
|
|
|
free(ctx);
|
|
|
|
// NOTE: do not free style since the default style is statically allocated,
|
|
// let the user take care of it instead
|
|
}
|
|
|
|
|
|
// TODO: add error codes
|
|
#define TEST_CTX(ctx) { if (!ctx) return -1; }
|
|
#define TEST_STR(s) { if (!s || !s[0]) return -1; }
|
|
|
|
|
|
int ug_ctx_set_displayinfo(ug_ctx_t *ctx, float scale, float ppi)
|
|
{
|
|
TEST_CTX(ctx);
|
|
if (scale <= 0 || ppi < 20.0)
|
|
return -1;
|
|
|
|
ctx->last_ppi = ctx->ppi;
|
|
ctx->last_ppm = ctx->ppd;
|
|
ctx->last_ppd = ctx->ppm;
|
|
|
|
ctx->ppm = PPI_PPM(scale, ppi);
|
|
ctx->ppd = PPI_PPM(scale, ppi);
|
|
ctx->ppi = ppi;
|
|
|
|
update_style_cache(ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ug_ctx_set_drawableregion(ug_ctx_t *ctx, ug_vec2_t size)
|
|
{
|
|
TEST_CTX(ctx);
|
|
if (size.w <= 0 || size.h <= 0)
|
|
return -1;
|
|
|
|
ctx->size.w = size.w;
|
|
ctx->size.h = size.h;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ug_ctx_set_style(ug_ctx_t *ctx, const ug_style_t *style)
|
|
{
|
|
TEST_CTX(ctx);
|
|
if (!style)
|
|
return -1;
|
|
// TODO: validate style
|
|
|
|
ctx->style = style;
|
|
update_style_cache(ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*=============================================================================*
|
|
* Container Operations *
|
|
*=============================================================================*/
|
|
|
|
#define STATEIS(cnt, f) ((cnt->flags & CNT_STATE_ALL) == f)
|
|
|
|
/*
|
|
* Container style: rca stands for absolute rectangle, it delimits the total
|
|
* drawable area, as such it does not include borders
|
|
*
|
|
* rca v Border Top v
|
|
* +-v--------------------------------------------------------+
|
|
* | +------------------------------------------------------+ |
|
|
* | | Titlebar | |
|
|
* | +------------------------------------------------------+ |
|
|
* | +------------------------------------------------------+ |
|
|
* | |\.................................................... | |
|
|
* | | \ ^ Border Top ^ . | |
|
|
* | | .\_ rect(0,0) . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . < Border Left . | |
|
|
* | | . + Margin Border Right >. | |
|
|
* | | . + Margin . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | . . | |
|
|
* | | .................................................... | |
|
|
* | +------------------------------------------------------+ |
|
|
* +----------------------------------------------------------+
|
|
* ^ Border Bottom ^
|
|
*/
|
|
|
|
|
|
// search a container by id int the stack and get it's address
|
|
static ug_container_t *search_container(ug_ctx_t *ctx, ug_id_t id)
|
|
{
|
|
ug_container_t *c = NULL;
|
|
for (int i = 0; i < ctx->cnt_stack.idx; i++) {
|
|
if (ctx->cnt_stack.items[i].id == id) {
|
|
c = &(ctx->cnt_stack.items[i]);
|
|
break;
|
|
}
|
|
}
|
|
return c;
|
|
}
|
|
|
|
|
|
// get a new or existing container handle
|
|
static ug_container_t *get_container(ug_ctx_t *ctx, ug_id_t id)
|
|
{
|
|
ug_container_t *c = search_container(ctx, id);
|
|
// if the container was not already there allocate a new one
|
|
if (!c) {
|
|
GET_FROM_STACK(ctx->cnt_stack, c);
|
|
ctx->cnt_stack.sorted = 0;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
// sort the conatiner stack so that floating conatiners always stay on top and
|
|
// the active floating conatiner i son top of everything
|
|
static void sort_containers(ug_ctx_t *ctx)
|
|
{
|
|
if (ctx->cnt_stack.sorted)
|
|
return;
|
|
int tot = ctx->cnt_stack.idx;
|
|
if (!tot)
|
|
return;
|
|
|
|
for (int i = 0; i < tot; i++) {
|
|
ug_container_t *cnt = &ctx->cnt_stack.items[i];
|
|
// move floating containers to the top
|
|
// FIXME: this is really shit
|
|
if (TEST(cnt->flags, UG_CNT_FLOATING)) {
|
|
int y = tot;
|
|
|
|
ug_container_t *s = ctx->cnt_stack.items;
|
|
ug_container_t c = *cnt;
|
|
|
|
if (ctx->active_cnt != c.id)
|
|
for (; y > 0 && TEST(s[y-1].flags, UG_CNT_FLOATING); y--);
|
|
|
|
if (i >= y)
|
|
continue;
|
|
|
|
memmove(&s[i], &s[i+1], (y - i) * sizeof(ug_container_t));
|
|
s[y-1] = c;
|
|
}
|
|
}
|
|
ctx->cnt_stack.sorted = 1;
|
|
}
|
|
|
|
|
|
// update the container position in the context area
|
|
// TODO: can we generalize position_container and position_element into one or
|
|
// more similar functions
|
|
static int position_container(ug_ctx_t *ctx, ug_container_t *cnt)
|
|
{
|
|
if (!TEST(cnt->flags, UG_CNT_FLOATING)) {
|
|
// if there is no space left propagate the error
|
|
if (ctx->origin.w <= 0 || ctx->origin.h <= 0)
|
|
return -1;
|
|
}
|
|
|
|
ug_rect_t *rect, *rca;
|
|
rect = &(cnt->rect);
|
|
rca = &(cnt->rca);
|
|
scale_rect(ctx, rect);
|
|
|
|
*rca = *rect;
|
|
|
|
const ug_style_t *s = ctx->style_px;
|
|
int b = SZ_INT(s->border.size);
|
|
int hh = SZ_INT(s->title.height);
|
|
ug_rect_t c = {
|
|
.x = ctx->origin.x + b,
|
|
.y = ctx->origin.y + b,
|
|
.w = ctx->origin.w - 2*b,
|
|
.h = ctx->origin.h - 2*b
|
|
};
|
|
|
|
// handle relative sizes
|
|
if (rect->w == 0) rca->w = c.w;
|
|
if (rect->h == 0) rca->h = c.h;
|
|
else if (TEST(cnt->flags, UG_CNT_MOVABLE)) rca->h += hh + b;
|
|
|
|
// handle borders, only draw borders on sides which can be used to resize
|
|
//if (TEST(cnt->flags, UG_CNT_RESIZE_LEFT)) {
|
|
// rca->w += b;
|
|
//}
|
|
//if (TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) {
|
|
// rca->w += b;
|
|
//}
|
|
//if (TEST(cnt->flags, UG_CNT_RESIZE_TOP)) {
|
|
// rca->h += b;
|
|
//}
|
|
//if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM)) {
|
|
// rca->h += b;
|
|
//}
|
|
//if (TEST(cnt->flags, UG_CNT_MOVABLE)) {
|
|
// rca->h += b;
|
|
//}
|
|
|
|
// if the container is not fixed than it can have positions outside of the
|
|
// main window, thus negative
|
|
if (!TEST(cnt->flags, UG_CNT_FLOATING)) {
|
|
// <0 -> relative to the right margin
|
|
if (rect->x < 0) rca->x = R_MARGIN(c, 0) - R_MARGIN((*rca), -1);
|
|
else rca->x = L_MARGIN(c, 0);
|
|
if (rect->y < 0) rca->y = B_MARGIN(c, 0) - B_MARGIN((*rca), -1);
|
|
else rca->y = T_MARGIN(c, 0);
|
|
|
|
// if the container is fixed than update the available space in
|
|
// the context
|
|
if (rect->w) {
|
|
if (rect->x >= 0)
|
|
ctx->origin.x = R_MARGIN((*rca), -b);
|
|
ctx->origin.w -= rca->w;
|
|
} else if (rect->h) {
|
|
if (rect->y >= 0)
|
|
ctx->origin.y = B_MARGIN((*rca), -b);
|
|
ctx->origin.h -= rca->h;
|
|
} else {
|
|
// width and height zero means fill everything
|
|
ctx->origin.w = ctx->origin.h = 0;
|
|
}
|
|
}
|
|
|
|
// set the correct element origin
|
|
cnt->c_orig.x = cnt->r_orig.x = cnt->space.x = L_MARGIN((*rca), 0);
|
|
cnt->c_orig.y = cnt->r_orig.y = cnt->space.y = T_MARGIN((*rca), 0);
|
|
if (TEST(cnt->flags, UG_CNT_MOVABLE)) {
|
|
cnt->c_orig.y += hh + SZ_INT(s->border.size);
|
|
cnt->r_orig.y += hh + SZ_INT(s->border.size);
|
|
cnt->space.y += hh + SZ_INT(s->border.size);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// TODO: make a set of fixed return values for the different states
|
|
// handle moving and resizing, return 1 if it had focus, 0 otherwise, negative
|
|
// return values represent errors
|
|
static int handle_container(ug_ctx_t *ctx, ug_container_t *cnt)
|
|
{
|
|
// if we are not the currently active container than definately we are not
|
|
// being moved or resized
|
|
if (ctx->active_cnt != cnt->id) {
|
|
cnt->flags &= ~CNT_STATE_ALL;
|
|
return 0;
|
|
}
|
|
|
|
// mouse pressed handle resize, for simplicity containers can only
|
|
// be resized from the bottom and right border
|
|
// TODO: change return value to indicate this case
|
|
if (!MOUSEHELD(ctx, UG_BTN_LEFT) ||
|
|
!TEST(cnt->flags, (RESIZEALL | UG_CNT_MOVABLE)))
|
|
return 1;
|
|
|
|
ug_rect_t *rect, *rca;
|
|
rect = &cnt->rect;
|
|
rca = &cnt->rca;
|
|
|
|
const ug_style_t *s = ctx->style_px;
|
|
int b = SZ_INT(s->border.size);
|
|
int m = SZ_INT(s->margin);
|
|
int hh = SZ_INT(s->title.height);
|
|
|
|
ug_vec2_t mpos = ctx->mouse.pos;
|
|
ug_rect_t clip;
|
|
|
|
// handle movable windows
|
|
if (TEST(cnt->flags, UG_CNT_MOVABLE)) {
|
|
clip = (ug_rect_t){
|
|
.x = L_MARGIN((*rca), 0),
|
|
.y = T_MARGIN((*rca), 0),
|
|
.w = rca->w,
|
|
.h = hh,
|
|
};
|
|
if (STATEIS(cnt, CNT_STATE_MOVING) ||
|
|
(STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) {
|
|
cnt->flags |= CNT_STATE_MOVING;
|
|
rect->x += ctx->mouse.delta.x;
|
|
rect->y += ctx->mouse.delta.y;
|
|
rca->x += ctx->mouse.delta.x;
|
|
rca->y += ctx->mouse.delta.y;
|
|
}
|
|
}
|
|
|
|
// lower diagonal resize
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM) && TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) {
|
|
clip = (ug_rect_t){
|
|
.x = R_MARGIN((*rca), m),
|
|
.y = B_MARGIN((*rca), m),
|
|
.w = b + m,
|
|
.h = b + m,
|
|
};
|
|
if (STATEIS(cnt, CNT_STATE_RESIZE_D) ||
|
|
(STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) {
|
|
cnt->flags |= CNT_STATE_RESIZE_D;
|
|
rect->h = MAX(10, rect->h + ctx->mouse.delta.y);
|
|
rca->h = MAX(10, rca->h + ctx->mouse.delta.y);
|
|
rect->w = MAX(10, rect->w + ctx->mouse.delta.x);
|
|
rca->w = MAX(10, rca->w + ctx->mouse.delta.x);
|
|
}
|
|
}
|
|
|
|
// right border resize
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) {
|
|
clip = (ug_rect_t){
|
|
.x = R_MARGIN((*rca), m),
|
|
.y = T_MARGIN((*rca), 0),
|
|
.w = b + m,
|
|
.h = rca->h,
|
|
};
|
|
if (STATEIS(cnt, CNT_STATE_RESIZE_R) ||
|
|
(STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) {
|
|
cnt->flags |= CNT_STATE_RESIZE_R;
|
|
rect->w = MAX(10, rect->w + ctx->mouse.delta.x);
|
|
rca->w = MAX(10, rca->w + ctx->mouse.delta.x);
|
|
}
|
|
}
|
|
|
|
// left border resize
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_LEFT)) {
|
|
clip = (ug_rect_t){
|
|
.x = L_MARGIN((*rca), -b),
|
|
.y = T_MARGIN((*rca), 0),
|
|
.w = b + m,
|
|
.h = rca->h,
|
|
};
|
|
if (STATEIS(cnt, CNT_STATE_RESIZE_L) ||
|
|
(STATEIS(cnt, 0) && INTERSECTS(mpos, clip))){
|
|
cnt->flags |= CNT_STATE_RESIZE_L;
|
|
|
|
if (rect->w - ctx->mouse.delta.x >= 10) {
|
|
rect->w -= ctx->mouse.delta.x;
|
|
if (TEST(cnt->flags, UG_CNT_MOVABLE))
|
|
rect->x += ctx->mouse.delta.x;
|
|
}
|
|
if (rca->w - ctx->mouse.delta.x >= 10) {
|
|
rca->w -= ctx->mouse.delta.x;
|
|
rca->x += ctx->mouse.delta.x;
|
|
}
|
|
}
|
|
}
|
|
|
|
// bottom border resize
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM)) {
|
|
clip = (ug_rect_t){
|
|
.x = L_MARGIN((*rca), 0),
|
|
.y = B_MARGIN((*rca), m),
|
|
.w = rca->w,
|
|
.h = b + m,
|
|
};
|
|
if (STATEIS(cnt, CNT_STATE_RESIZE_B) ||
|
|
(STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) {
|
|
cnt->flags |= CNT_STATE_RESIZE_B;
|
|
rect->h = MAX(10, rect->h + ctx->mouse.delta.y);
|
|
rca->h = MAX(10, rca->h + ctx->mouse.delta.y);
|
|
}
|
|
}
|
|
|
|
// top border resize
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_TOP)) {
|
|
clip = (ug_rect_t){
|
|
.x = L_MARGIN((*rca), 0),
|
|
.y = T_MARGIN((*rca), -b),
|
|
.w = rca->w,
|
|
.h = b + m,
|
|
};
|
|
if (STATEIS(cnt, CNT_STATE_RESIZE_T) ||
|
|
(STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) {
|
|
cnt->flags |= CNT_STATE_RESIZE_T;
|
|
|
|
if (rect->h - ctx->mouse.delta.y >= 10) {
|
|
rect->h -= ctx->mouse.delta.y;
|
|
if (TEST(cnt->flags, UG_CNT_MOVABLE))
|
|
rect->y += ctx->mouse.delta.y;
|
|
}
|
|
if (rca->h - ctx->mouse.delta.y >= 10) {
|
|
rca->h -= ctx->mouse.delta.y;
|
|
rca->y += ctx->mouse.delta.y;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we were not resized but we are still active it means we are doing
|
|
// something to the contained elements, as such set state to none
|
|
if (STATEIS(cnt, 0))
|
|
cnt->flags |= CNT_STATE_NONE;
|
|
|
|
// TODO: what if I want to close a floating container?
|
|
// Maybe add a UG_CNT_CLOSABLE flag?
|
|
|
|
// TODO: what about scrolling? how do we know if we need to draw
|
|
// a scroll bar? Maybe add that information inside the
|
|
// container structure
|
|
return 1;
|
|
}
|
|
|
|
#define EXPAND(r, b) {r.x-=b,r.y-=b,r.w+=2*b,r.h+=2*b;}
|
|
|
|
static void draw_container(ug_ctx_t *ctx, ug_container_t *cnt, const char *text)
|
|
{
|
|
ug_rect_t rect;
|
|
const ug_style_t *s = ctx->style_px;
|
|
int b = SZ_INT(s->border.size);
|
|
int hh = SZ_INT(s->title.height);
|
|
int ts = SZ_INT(s->title.font_size);
|
|
|
|
// push main body
|
|
rect = cnt->rca;
|
|
EXPAND(rect, b);
|
|
push_rect_command(ctx, &rect, s->color.bg);
|
|
|
|
// push outline
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_LEFT)) {
|
|
rect = cnt->rca;
|
|
rect.x -= b;
|
|
rect.y -= b,
|
|
rect.h += 2*b;
|
|
rect.w = b;
|
|
push_rect_command(ctx, &rect, s->border.color);
|
|
}
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) {
|
|
rect = cnt->rca;
|
|
rect.x += rect.w;
|
|
rect.y -= b;
|
|
rect.h += 2*b;
|
|
rect.w = b;
|
|
push_rect_command(ctx, &rect, s->border.color);
|
|
}
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_TOP)) {
|
|
rect = cnt->rca;
|
|
rect.y -= b;
|
|
rect.x -= b;
|
|
rect.h = b;
|
|
rect.w += 2*b;
|
|
push_rect_command(ctx, &rect, s->border.color);
|
|
}
|
|
if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM)) {
|
|
rect = cnt->rca;
|
|
rect.x -= b;
|
|
rect.y += rect.h;
|
|
rect.h = b;
|
|
rect.w += 2*b;
|
|
push_rect_command(ctx, &rect, s->border.color);
|
|
}
|
|
// titlebar
|
|
if (TEST(cnt->flags, UG_CNT_MOVABLE)) {
|
|
// titlebar area
|
|
rect = cnt->rca;
|
|
rect.h = hh;
|
|
push_rect_command(ctx, &rect, s->title.color.bg);
|
|
// titlebar border
|
|
rect.y += hh;
|
|
rect.h = b;
|
|
push_rect_command(ctx, &rect, s->border.color);
|
|
if (text) {
|
|
// TODO: center text
|
|
push_text_command(ctx,
|
|
(ug_vec2_t){.x = rect.x, .y = rect.y},
|
|
ts, s->title.color.fg, text);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// a floating container can be placed anywhere and can be resized, acts like a
|
|
// window inside another window
|
|
int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
// TODO: verify div
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_container_t *cnt = get_container(ctx, id);
|
|
|
|
// maybe the name address was changed so always overwrite it
|
|
cnt->name = name;
|
|
if (cnt->id) {
|
|
// nothing? maybe we can skip updating all dimensions and stuff
|
|
} else {
|
|
cnt->id = id;
|
|
cnt->rect = div_to_rect(ctx, &div);
|
|
cnt->flags = UG_CNT_FLOATING | RESIZEALL | UG_CNT_MOVABLE |
|
|
UG_CNT_SCROLL_X | UG_CNT_SCROLL_Y;
|
|
}
|
|
|
|
// TODO: what about error codes and meaning?
|
|
if (position_container(ctx, cnt)) {
|
|
DELETE_FROM_STACK(ctx->cnt_stack, cnt);
|
|
return -1;
|
|
}
|
|
|
|
// Select current conatiner
|
|
ctx->selected_cnt = id;
|
|
|
|
return handle_container(ctx, cnt);
|
|
}
|
|
|
|
|
|
int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
// TODO: verify div
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_container_t *cnt = get_container(ctx, id);
|
|
|
|
// maybe the name address was changed so always overwrite it
|
|
cnt->name = name;
|
|
if (cnt->id) {
|
|
// nothing? maybe we can skip updating all dimensions and stuff
|
|
} else {
|
|
cnt->id = id;
|
|
cnt->rect = div_to_rect(ctx, &div);
|
|
cnt->flags = UG_CNT_FLOATING;
|
|
}
|
|
|
|
// TODO: what about error codes and meaning?
|
|
if (position_container(ctx, cnt)) {
|
|
DELETE_FROM_STACK(ctx->cnt_stack, cnt);
|
|
return -1;
|
|
}
|
|
|
|
// Select current conatiner
|
|
ctx->selected_cnt = id;
|
|
|
|
return handle_container(ctx, cnt);
|
|
}
|
|
|
|
|
|
int ug_container_sidebar(ug_ctx_t *ctx, const char *name, ug_size_t size, int side)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_container_t *cnt = get_container(ctx, id);
|
|
|
|
// maybe the name address was changed so always overwrite it
|
|
cnt->name = name;
|
|
if (cnt->id) {
|
|
// nothing? maybe we can skip updating all dimensions and stuff
|
|
} else {
|
|
cnt->id = id;
|
|
cnt->flags = UG_CNT_SCROLL_X | UG_CNT_SCROLL_Y;
|
|
ug_rect_t rect = {0};
|
|
switch (side) {
|
|
case UG_SIDE_BOTTOM:
|
|
cnt->flags |= UG_CNT_RESIZE_TOP;
|
|
rect.y = -1;
|
|
rect.h = size_to_px(ctx, size);
|
|
break;
|
|
case UG_SIDE_TOP:
|
|
cnt->flags |= UG_CNT_RESIZE_BOTTOM;
|
|
rect.h = size_to_px(ctx, size);
|
|
break;
|
|
case UG_SIDE_RIGHT:
|
|
cnt->flags |= UG_CNT_RESIZE_LEFT;
|
|
rect.x = -1;
|
|
rect.w = size_to_px(ctx, size);
|
|
break;
|
|
case UG_SIDE_LEFT:
|
|
cnt->flags |= UG_CNT_RESIZE_RIGHT;
|
|
rect.w = size_to_px(ctx, size);
|
|
break;
|
|
default: return -1;
|
|
}
|
|
cnt->rect = rect;
|
|
}
|
|
|
|
if (position_container(ctx, cnt)) {
|
|
DELETE_FROM_STACK(ctx->cnt_stack, cnt);
|
|
return -1;
|
|
}
|
|
|
|
// Select current conatiner
|
|
ctx->selected_cnt = id;
|
|
|
|
return handle_container(ctx, cnt);
|
|
}
|
|
|
|
int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_container_t *cnt = get_container(ctx, id);
|
|
|
|
// maybe the name address was changed so always overwrite it
|
|
cnt->name = name;
|
|
if (cnt->id) {
|
|
// nothing? maybe we can skip updating all dimensions and stuff
|
|
} else {
|
|
cnt->id = id;
|
|
cnt->flags = 0;
|
|
ug_rect_t rect = {
|
|
.x = 0, .y = 0,
|
|
.w = 0, .h = size_to_px(ctx, height),
|
|
};
|
|
cnt->rect = rect;
|
|
}
|
|
|
|
if (position_container(ctx, cnt)) {
|
|
DELETE_FROM_STACK(ctx->cnt_stack, cnt);
|
|
return -1;
|
|
}
|
|
|
|
// Select current conatiner
|
|
ctx->selected_cnt = id;
|
|
|
|
return handle_container(ctx, cnt);
|
|
}
|
|
|
|
|
|
int ug_container_body(ug_ctx_t *ctx, const char *name)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_container_t *cnt = get_container(ctx, id);
|
|
|
|
// maybe the name address was changed so always overwrite it
|
|
cnt->name = name;
|
|
if (cnt->id) {
|
|
// nothing? maybe we can skip updating all dimensions and stuff
|
|
} else {
|
|
cnt->id = id;
|
|
cnt->flags = 0;
|
|
cnt->rect = (ug_rect_t){0};
|
|
}
|
|
|
|
if (position_container(ctx, cnt)) {
|
|
DELETE_FROM_STACK(ctx->cnt_stack, cnt);
|
|
return -1;
|
|
}
|
|
|
|
// Select current conatiner
|
|
ctx->selected_cnt = id;
|
|
|
|
return handle_container(ctx, cnt);
|
|
}
|
|
|
|
|
|
// TODO: return an error indicating that no container exists with that name
|
|
int ug_container_remove(ug_ctx_t *ctx, const char *name)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
ug_id_t id = ctx->selected_cnt;
|
|
if (name)
|
|
id = hash(name, strlen(name));
|
|
|
|
ug_container_t *c = search_container(ctx, id);
|
|
if (c) {
|
|
c->flags |= CNT_STATE_DELETE;
|
|
return 0;
|
|
} else return -1;
|
|
}
|
|
|
|
|
|
ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name)
|
|
{
|
|
ug_rect_t r = {0};
|
|
if(!ctx)
|
|
return r;
|
|
|
|
ug_id_t id = ctx->selected_cnt;
|
|
if (name)
|
|
id = hash(name, strlen(name));
|
|
|
|
ug_container_t *c = search_container(ctx, id);
|
|
if (c)
|
|
r = c->rca;
|
|
return r;
|
|
}
|
|
|
|
|
|
/*=============================================================================*
|
|
* Frame Handling *
|
|
*=============================================================================*/
|
|
|
|
|
|
// At the beginning of a frame assume that all input has been passed to the context
|
|
// update the mouse delta and reset the command stack
|
|
int ug_frame_begin(ug_ctx_t *ctx)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
// update mouse delta
|
|
ctx->mouse.delta.x = ctx->mouse.pos.x - ctx->mouse.last_pos.x;
|
|
ctx->mouse.delta.y = ctx->mouse.pos.y - ctx->mouse.last_pos.y;
|
|
|
|
// clear command stack
|
|
RESET_STACK(ctx->cmd_stack);
|
|
|
|
// update the origin
|
|
ctx->origin.x = 0;
|
|
ctx->origin.y = 0;
|
|
ctx->origin.w = ctx->size.w;
|
|
ctx->origin.h = ctx->size.h;
|
|
|
|
// update hover index
|
|
printf("Container Stack: active %x\n", ctx->active_cnt);
|
|
ug_vec2_t v = ctx->mouse.pos;
|
|
for (int i = 0; i < ctx->cnt_stack.idx; i++) {
|
|
ug_container_t *c = &ctx->cnt_stack.items[i];
|
|
|
|
printf("[%d]: %x\n", i, c->id);
|
|
if (TEST(c->flags, CNT_STATE_DELETE)) {
|
|
DELETE_FROM_STACK(ctx->cnt_stack, c);
|
|
// FIXME: this should be fine since all elements in the
|
|
// stack are moved back and c now points to the
|
|
// container that would be next
|
|
}
|
|
|
|
ug_rect_t r = c->rca;
|
|
EXPAND(r, SZ_INT(ctx->style_px->border.size));
|
|
if (INTERSECTS(v, r))
|
|
ctx->hover_cnt = c->id;
|
|
}
|
|
printf("\n");
|
|
|
|
if (ctx->last_active_cnt && ctx->active_cnt != ctx->last_active_cnt)
|
|
ctx->cnt_stack.sorted = 0;
|
|
|
|
// update active container
|
|
if (MOUSEDOWN(ctx, UG_BTN_LEFT)) {
|
|
ctx->active_cnt = ctx->hover_cnt;
|
|
} else if (MOUSEUP(ctx, UG_BTN_LEFT)) {
|
|
ctx->last_active_cnt = ctx->active_cnt;
|
|
ctx->active_cnt = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// FIXME: reoreder code such that this prototype is not needed
|
|
void draw_elements(ug_ctx_t *, ug_container_t *);
|
|
|
|
// At the end of a frame reset inputs
|
|
int ug_frame_end(ug_ctx_t *ctx)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
// before drawing floating contaners need to be drawn on top of the others
|
|
sort_containers(ctx);
|
|
for (int i = 0; i < ctx->cnt_stack.idx; i++) {
|
|
ug_container_t *c = &ctx->cnt_stack.items[i];
|
|
// draw containers
|
|
draw_container(ctx, c, c->name);
|
|
// TODO: add a debug flag
|
|
// debug draw space used by elements
|
|
push_rect_command(ctx, &c->space, (ug_color_t)RGBA_FORMAT(0x0000abf0));
|
|
// draw elements
|
|
draw_elements(ctx, c);
|
|
// reset the hover element
|
|
c->hover_elem = 0;
|
|
// reset the layout to row
|
|
c->flags &= ~(CNT_LAYOUT_COLUMN);
|
|
// reset used space
|
|
c->space = (ug_rect_t){0};
|
|
}
|
|
|
|
ctx->input_text[0] = '\0';
|
|
ctx->key.update = 0;
|
|
ctx->mouse.hold ^= ctx->mouse.update;
|
|
ctx->mouse.update = 0;
|
|
ctx->mouse.scroll_delta = (ug_vec2_t){0};
|
|
ctx->mouse.last_pos = ctx->mouse.pos;
|
|
|
|
// reset hover, it has to be calculated at frame beginning
|
|
// FIXME: reeeeeally?
|
|
//ctx->hover_cnt = 0;
|
|
|
|
ctx->last_ppi = ctx->ppi;
|
|
ctx->last_ppm = ctx->ppm;
|
|
ctx->last_ppd = ctx->ppd;
|
|
|
|
ctx->frame++;
|
|
|
|
// deselect container
|
|
ctx->selected_cnt = 0;
|
|
|
|
ctx->cmd_it = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*=============================================================================*
|
|
* Layouting *
|
|
*=============================================================================*/
|
|
|
|
|
|
/*
|
|
* Container layout:
|
|
* A partition in the space taken up by the conglomerate of elements in
|
|
* between calls to new_row or new_column (no backing state, just an idea)
|
|
* +--------------------------------------------------------+
|
|
* | .<- r_orig |
|
|
* | . |
|
|
* | previous element . |
|
|
* | . |
|
|
* |.............................. |
|
|
* |^ |
|
|
* |c_orig |
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* | |
|
|
* +--------------------------------------------------------+
|
|
*
|
|
* Element Style:
|
|
*
|
|
* rca margin
|
|
* +-v--------------+<--->+----------------+
|
|
* | +------------+ | | +------------+ |
|
|
* | | | | | | | |
|
|
* | | | | | | | |
|
|
* | | | | | | | |
|
|
* | +------------+ | | +------------+ |
|
|
* +----------------+ +----------------+
|
|
* <->
|
|
* border
|
|
*/
|
|
|
|
|
|
#define GET_SELECTED_CONTAINER(ctx, cp) \
|
|
{ \
|
|
cp = search_container(ctx, ctx->selected_cnt); \
|
|
if (!cp) \
|
|
return -1; \
|
|
}
|
|
|
|
|
|
// set the layout to row
|
|
int ug_layout_row(ug_ctx_t *ctx)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
ug_container_t *cp;
|
|
GET_SELECTED_CONTAINER(ctx, cp);
|
|
cp->flags &= ~CNT_LAYOUT_COLUMN;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// set the layout to column
|
|
int ug_layout_column(ug_ctx_t *ctx)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
ug_container_t *cp;
|
|
GET_SELECTED_CONTAINER(ctx, cp);
|
|
cp->flags |= CNT_LAYOUT_COLUMN;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ug_layout_next_row(ug_ctx_t *ctx)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
ug_container_t *cp;
|
|
GET_SELECTED_CONTAINER(ctx, cp);
|
|
|
|
cp->c_orig.x = cp->r_orig.x = L_MARGIN(cp->space, 0);
|
|
cp->c_orig.y = cp->r_orig.y = B_MARGIN(cp->space, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int ug_layout_next_column(ug_ctx_t *ctx)
|
|
{
|
|
TEST_CTX(ctx);
|
|
|
|
ug_container_t *cp;
|
|
GET_SELECTED_CONTAINER(ctx, cp);
|
|
|
|
cp->c_orig.x = cp->r_orig.x = R_MARGIN(cp->space, 0);
|
|
cp->c_orig.y = cp->r_orig.y = T_MARGIN(cp->space, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*=============================================================================*
|
|
* Elements *
|
|
*=============================================================================*/
|
|
|
|
// search an element by id in the container's element stack and get it's address
|
|
static ug_element_t *search_element(ug_container_t *cnt, ug_id_t id)
|
|
{
|
|
ug_element_t *e = NULL;
|
|
for (int i = 0; i < cnt->elem_stack.idx; i++) {
|
|
if (cnt->elem_stack.items[i].id == id) {
|
|
e = &(cnt->elem_stack.items[i]);
|
|
break;
|
|
}
|
|
}
|
|
return e;
|
|
}
|
|
|
|
|
|
// get a new or existing container handle
|
|
static ug_element_t *get_element(ug_container_t *cnt, ug_id_t id)
|
|
{
|
|
ug_element_t *e = search_element(cnt, id);
|
|
if (!e) {
|
|
GET_FROM_STACK(cnt->elem_stack, e);
|
|
cnt->elem_stack.sorted = 0;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
|
|
// update the element's position in the container area
|
|
static int position_element(ug_ctx_t *ctx, ug_container_t *cnt, ug_element_t *elem)
|
|
{
|
|
|
|
// if there is no space then return
|
|
if (TEST(cnt->flags, CNT_LAYOUT_COLUMN)) {
|
|
if (cnt->space.w >= cnt->rca.w)
|
|
return -1;
|
|
} else {
|
|
if (cnt->space.h >= cnt->rca.h)
|
|
return -1;
|
|
}
|
|
|
|
|
|
ug_rect_t *rect, *rca;
|
|
rect = &(elem->rect);
|
|
rca = &(elem->rca);
|
|
scale_rect(ctx, rect);
|
|
|
|
*rca = *rect;
|
|
|
|
const ug_style_t *s = ctx->style_px;
|
|
int b = SZ_INT(s->border.size);
|
|
int m = SZ_INT(s->margin);
|
|
int cx, cy, cw, ch;
|
|
int eb = 0;
|
|
|
|
switch (elem->type) {
|
|
case UG_ELEM_BUTTON: eb = SZ_INT(s->btn.border); break;
|
|
default: break;
|
|
}
|
|
|
|
// FIXME: this does not work
|
|
cw = MAX(cnt->rca.w - cnt->space.w, 0);
|
|
ch = MAX(cnt->rca.h - cnt->space.h, 0);
|
|
if (TEST(cnt->flags, CNT_LAYOUT_COLUMN)) {
|
|
cx = cnt->c_orig.x;
|
|
cy = cnt->c_orig.y;
|
|
} else {
|
|
cx = cnt->r_orig.x;
|
|
cy = cnt->r_orig.y;
|
|
}
|
|
|
|
// handle relative sizes
|
|
if (rect->w == 0) rca->w = cw - 2*eb;
|
|
if (rect->h == 0) rca->h = ch - 2*eb;
|
|
// for elements x and y are offsets
|
|
rca->x = cx + rect->x + eb + m;
|
|
rca->y = cy + rect->y + eb + m;
|
|
|
|
// if the element was put outside of the container return, this can happen
|
|
// because of the element offset
|
|
if (OUTSIDE((*rca), 0, cnt->rca, b))
|
|
return -1;
|
|
|
|
elem->flags &= ~(ELEM_CLIPPED);
|
|
if (crop_rect(rca, &cnt->rca))
|
|
elem->flags |= ELEM_CLIPPED;
|
|
|
|
if (TEST(cnt->flags, CNT_LAYOUT_COLUMN)) {
|
|
cnt->c_orig.y = B_MARGIN((*rca), 0) + eb;
|
|
cnt->r_orig.x = R_MARGIN((*rca), 0) + eb;
|
|
cnt->r_orig.y = T_MARGIN((*rca), 0) + eb;
|
|
|
|
} else {
|
|
cnt->r_orig.x = R_MARGIN((*rca), 0) + eb;
|
|
cnt->c_orig.x = L_MARGIN((*rca), 0) + eb;
|
|
cnt->c_orig.y = B_MARGIN((*rca), 0) + eb;
|
|
}
|
|
|
|
// expand used space
|
|
if (R_MARGIN(cnt->space, 0) < R_MARGIN((*rca), -eb))
|
|
cnt->space.w = R_MARGIN((*rca), -eb) - L_MARGIN(cnt->space, 0);
|
|
if (B_MARGIN(cnt->space, 0) < B_MARGIN((*rca), -eb))
|
|
cnt->space.h = B_MARGIN((*rca), -eb) - T_MARGIN(cnt->space, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
// TODO: have a map of function pointers for each element type instead of this
|
|
// generic function
|
|
int handle_element(ug_ctx_t *ctx, ug_container_t *cnt, ug_element_t *elem)
|
|
{
|
|
// if the contaniner is not the current hover then do nothing, think about
|
|
// floating cont. over bodies
|
|
if (ctx->hover_cnt != cnt->id)
|
|
return 0;
|
|
|
|
if (INTERSECTS(ctx->mouse.pos, elem->rca)) {
|
|
cnt->hover_elem = elem->id;
|
|
if (MOUSEDOWN(ctx, BTN_ANY))
|
|
cnt->selected_elem = elem->id;
|
|
} else {
|
|
if (cnt->selected_elem != elem->id)
|
|
return 0;
|
|
}
|
|
// TODO: handle different types of elements
|
|
|
|
// TODO: return which button was held
|
|
return 1;
|
|
}
|
|
|
|
|
|
void draw_elements(ug_ctx_t *ctx, ug_container_t *cnt)
|
|
{
|
|
const ug_style_t *s = ctx->style_px;
|
|
|
|
for (int i = 0; i < cnt->elem_stack.idx; i++) {
|
|
ug_element_t *e = &(cnt->elem_stack.items[i]);
|
|
ug_color_t col = RGB_FORMAT(0), bcol = RGB_FORMAT(0);
|
|
ug_color_t txtcol = RGB_FORMAT(0);
|
|
ug_rect_t r = e->rca;
|
|
int eb = 0, ts = 0;
|
|
unsigned char draw_br = 0, draw_fg = 0, draw_txt = 0;
|
|
|
|
switch (e->type) {
|
|
case UG_ELEM_BUTTON:
|
|
col = cnt->hover_elem == e->id ? s->btn.color.act : s->btn.color.bg;
|
|
bcol = cnt->selected_elem == e->id && ctx->hover_cnt == cnt->id ? s->btn.color.sel : s->btn.color.br;
|
|
txtcol = s->btn.color.fg;
|
|
ts = SZ_INT(s->btn.font_size);
|
|
eb = SZ_INT(ctx->style_px->btn.border);
|
|
draw_br = draw_fg = draw_txt = 1;
|
|
break;
|
|
case UG_ELEM_TXTBTN:
|
|
ts = SZ_INT(s->btn.font_size);
|
|
txtcol = s->color.fg;
|
|
draw_txt = 1;
|
|
default: break;
|
|
}
|
|
|
|
if (draw_br) {
|
|
r = (ug_rect_t){
|
|
.x = e->rca.x - eb,
|
|
.y = e->rca.y - eb,
|
|
.w = e->rca.w + 2*eb,
|
|
.h = e->rca.h + 2*eb,
|
|
};
|
|
if (TEST(e->flags, ELEM_CLIPPED))
|
|
crop_rect(&r, &cnt->rca);
|
|
push_rect_command(ctx, &r, bcol);
|
|
}
|
|
if (draw_fg) {
|
|
r = e->rca;
|
|
push_rect_command(ctx, &r, col);
|
|
}
|
|
if (draw_txt) {
|
|
push_text_command(ctx,
|
|
(ug_vec2_t){
|
|
.x = e->rca.x+eb,
|
|
.y = e->rca.y+eb+e->rca.h/2
|
|
},
|
|
ts,
|
|
txtcol,
|
|
e->btn.txt);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
|
|
ug_container_t *cp;
|
|
GET_SELECTED_CONTAINER(ctx, cp);
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_element_t *elem = get_element(cp, id);
|
|
|
|
// FIXME: we don't always need to do everything
|
|
elem->id = id;
|
|
elem->type = UG_ELEM_BUTTON;
|
|
elem->rect = div_to_rect(ctx, &dim);
|
|
elem->name = name;
|
|
elem->btn.txt = txt;
|
|
|
|
// FIXME: what about error codes?
|
|
if (position_element(ctx, cp, elem)) {
|
|
DELETE_FROM_STACK(cp->elem_stack, elem);
|
|
return -1;
|
|
}
|
|
|
|
return handle_element(ctx, cp, elem);
|
|
}
|
|
|
|
|
|
// text-only button
|
|
int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim)
|
|
{
|
|
TEST_CTX(ctx);
|
|
TEST_STR(name);
|
|
TEST_STR(txt);
|
|
|
|
ug_container_t *cp;
|
|
GET_SELECTED_CONTAINER(ctx, cp);
|
|
|
|
ug_id_t id = hash(name, strlen(name));
|
|
ug_element_t *elem = get_element(cp, id);
|
|
|
|
// FIXME: we don't always need to do everything
|
|
elem->id = id;
|
|
elem->type = UG_ELEM_TXTBTN;
|
|
elem->rect = div_to_rect(ctx, &dim);
|
|
elem->name = name;
|
|
elem->btn.txt = txt;
|
|
|
|
// FIXME: what about error codes?
|
|
if (position_element(ctx, cp, elem)) {
|
|
DELETE_FROM_STACK(cp->elem_stack, elem);
|
|
return -1;
|
|
}
|
|
|
|
return handle_element(ctx, cp, elem);
|
|
}
|
|
|