#include #include #include #include #include #include #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); }