From 1e31f06200071139ad3fb193fea3550387454af8 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 14:10:06 -0500 Subject: [PATCH 01/26] clone glui --- Makefile.common | 9 +++++++++ griffin/griffin.c | 5 +++++ menu/menu_driver.c | 3 +++ menu/menu_driver.h | 1 + 4 files changed, 18 insertions(+) diff --git a/Makefile.common b/Makefile.common index f716af96fa..4b7edd0935 100644 --- a/Makefile.common +++ b/Makefile.common @@ -370,6 +370,9 @@ ifeq ($(HAVE_RGUI)$(HAVE_GL_CONTEXT), 11) ifeq ($(HAVE_MATERIALUI),) HAVE_MATERIALUI = 1 endif + ifeq ($(HAVE_WIMP),) + HAVE_WIMP = 1 + endif ifeq ($(HAVE_XMB),) HAVE_XMB = 1 @@ -377,6 +380,7 @@ ifeq ($(HAVE_RGUI)$(HAVE_GL_CONTEXT), 11) else HAVE_ZARCH = 0 HAVE_MATERIALUI = 0 + HAVE_WIMP = 0 HAVE_XMB = 0 endif @@ -388,6 +392,11 @@ ifeq ($(HAVE_MATERIALUI), 1) OBJ += menu/drivers/materialui.o DEFINES += -DHAVE_MATERIALUI endif +ifeq ($(HAVE_WIMP), 1) + OBJ += deps/zahnrad/zahnrad.o + OBJ += menu/drivers/wimp.o + DEFINES += -DHAVE_WIMP +endif ifeq ($(HAVE_ZARCH), 1) OBJ += menu/drivers/zarch.o DEFINES += -DHAVE_ZARCH diff --git a/griffin/griffin.c b/griffin/griffin.c index 592286bc44..8c2cbfd50e 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -845,6 +845,11 @@ MENU #include "../menu/drivers/materialui.c" #endif +#ifdef HAVE_WIMP +#include "../deps/zahnrad/zahnrad.c" +#include "../menu/drivers/wimp.c" +#endif + #ifdef HAVE_ZARCH #include "../menu/drivers/zarch.c" #endif diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 7e5aa87616..2660beb1de 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -45,6 +45,9 @@ static const menu_ctx_driver_t *menu_ctx_drivers[] = { #if defined(HAVE_MATERIALUI) &menu_ctx_mui, #endif +#if defined(HAVE_WIMP) + &menu_ctx_wimp, +#endif #if defined(HAVE_XMB) &menu_ctx_xmb, #endif diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 87c35c5759..4288defe3d 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -447,6 +447,7 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data); extern menu_ctx_driver_t menu_ctx_xui; extern menu_ctx_driver_t menu_ctx_rgui; extern menu_ctx_driver_t menu_ctx_mui; +extern menu_ctx_driver_t menu_ctx_wimp; extern menu_ctx_driver_t menu_ctx_xmb; extern menu_ctx_driver_t menu_ctx_zarch; extern menu_ctx_driver_t menu_ctx_null; From 50929c9809e23a62c69509bde5116005430077a4 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 14:13:49 -0500 Subject: [PATCH 02/26] add gui toolkit --- Makefile.common | 1 + deps/zahnrad/CONTRIBUTING.md | 43 + deps/zahnrad/LICENSE | 17 + deps/zahnrad/Readme.md | 89 + deps/zahnrad/font/DroidSans.ttf | Bin 0 -> 190044 bytes deps/zahnrad/font/Roboto-Bold.ttf | Bin 0 -> 135820 bytes deps/zahnrad/font/Roboto-Light.ttf | Bin 0 -> 140276 bytes deps/zahnrad/font/Roboto-Regular.ttf | Bin 0 -> 145348 bytes deps/zahnrad/stb_rect_pack.h | 545 ++ deps/zahnrad/stb_truetype.h | 3218 +++++++ deps/zahnrad/zahnrad.c | 11127 +++++++++++++++++++++++++ deps/zahnrad/zahnrad.h | 1631 ++++ 12 files changed, 16671 insertions(+) create mode 100644 deps/zahnrad/CONTRIBUTING.md create mode 100644 deps/zahnrad/LICENSE create mode 100644 deps/zahnrad/Readme.md create mode 100644 deps/zahnrad/font/DroidSans.ttf create mode 100644 deps/zahnrad/font/Roboto-Bold.ttf create mode 100644 deps/zahnrad/font/Roboto-Light.ttf create mode 100644 deps/zahnrad/font/Roboto-Regular.ttf create mode 100644 deps/zahnrad/stb_rect_pack.h create mode 100644 deps/zahnrad/stb_truetype.h create mode 100644 deps/zahnrad/zahnrad.c create mode 100644 deps/zahnrad/zahnrad.h diff --git a/Makefile.common b/Makefile.common index 4b7edd0935..dbdb78a0cd 100644 --- a/Makefile.common +++ b/Makefile.common @@ -370,6 +370,7 @@ ifeq ($(HAVE_RGUI)$(HAVE_GL_CONTEXT), 11) ifeq ($(HAVE_MATERIALUI),) HAVE_MATERIALUI = 1 endif + ifeq ($(HAVE_WIMP),) HAVE_WIMP = 1 endif diff --git a/deps/zahnrad/CONTRIBUTING.md b/deps/zahnrad/CONTRIBUTING.md new file mode 100644 index 0000000000..a806978101 --- /dev/null +++ b/deps/zahnrad/CONTRIBUTING.md @@ -0,0 +1,43 @@ +CONTRIBUTING +============ +## Submitting changes +Please send a GitHub Pull Request with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)). + +## Features +If you have an idea for new features just [open an issue](https://github.com/vurtun/zahnrad/issues) with your suggestion. + * Find and correct spelling mistakes + * Add (insert your favorite platform or render backend here) demo implementation (some possibilities: DirectX 9/DirectX 10/DirectX 11 and win32 with OpenGL) + * Add clipboard user callbacks back into all demos + * Add additional widgets [some possible widgets](http://doc.qt.io/qt-5/widget-classes.html#the-widget-classes) + * Add support for multiple pointers for touch input devices (probably requires to rewrite mouse handling in `struct zr_input`) + * Extend xlib demo to support image drawing with arbitrary image width and height + * Change cursor in `zr_widget_edit_box` and `zr_widget_edit_field` to thin standard cursor version used in editors + * Extend piemenu to support submenus (another ring around the first ring or something like [this:](http://gdj.gdj.netdna-cdn.com/wp-content/uploads/2013/02/ui+concepts+13.gif)) and turn it into a default library widget. + * Add label describing the currently active piemenu entry + * Maybe write a piemenu text only version for platforms that do not want or can use images + * Rewrite the chart API to support a better range of charts (maybe take notes from Javascript chart frameworks) + * Create an API to allow scaling between groups (maybe extend and convert the demo example) + * Add multiple Tab support (maybe use `zr_group` and add a header) + * Come up with a better way to provide and create widget and window styles + * Add tables with scaleable column width + * Extend context to not only support overlapping windows but tiled windows as well + +## Bugs + * Seperator widget is currently bugged and does not work as intended + * Text handling is still a little bit janky and probably needs to be further tested and polished + * `zr_edit_buffer` with multiline flag is bugged for '\n', need to differentiate between visible and non-visible characters + +## Coding conventions + * Only use C89 (ANSI C) + * Do not use any compiler specific extensions + * For indent use four spaces + * Do not typedef structs, unions and enums + * Variable, object and function names should always be lowercase and use underscores instead of camel case + * Whitespace after for, while, if, do and switch + * Always use parentheses if you use the sizeof operator (e.g: sizeof(struct zr_context) and not sizeof struct zr_context) + * Beginning braces on the new line for functions and on the same line otherwise. + * If function becomes to big either a.) create a subblock inside the function and comment or b.) write a functional function + * Only use fixed size types (zr_uint, zr_size, ...) if you really need to and use basic types otherwise + * Do not include any header files in either zahnrad.h or zahnrad.c + * Do not add dependencies rather write your own version if possible + * Write correct commit messages: (http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) diff --git a/deps/zahnrad/LICENSE b/deps/zahnrad/LICENSE new file mode 100644 index 0000000000..4f0e12e925 --- /dev/null +++ b/deps/zahnrad/LICENSE @@ -0,0 +1,17 @@ +Copyright (c) 2016 Micha Mettke + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/deps/zahnrad/Readme.md b/deps/zahnrad/Readme.md new file mode 100644 index 0000000000..3722a8477f --- /dev/null +++ b/deps/zahnrad/Readme.md @@ -0,0 +1,89 @@ +# Zahnrad +[![Coverity Status](https://scan.coverity.com/projects/5863/badge.svg)](https://scan.coverity.com/projects/5863) + +This is a minimal state immediate mode graphical user interface toolkit +written in ANSI C and licensed under zlib. It was designed as a simple embeddable user interface for +application and does not have any direct dependencies, +a default renderbackend or OS window and input handling but instead provides a very modular +library approach by using simple input state for input and draw +commands describing primitive shapes as output. So instead of providing a +layered library that tries to abstract over a number of platform and +render backends it only focuses on the actual UI. + +## Features +- Immediate mode graphical user interface toolkit +- Written in C89 (ANSI C) +- Small codebase (~9kLOC) +- Focus on portability, efficiency and simplicity +- No dependencies (not even the standard library) +- No global or hidden state +- Configurable style and colors +- UTF-8 support + +## Optional +- Vertex buffer output +- Font handling + +## Building +The library is self-contained within four different files that only have to be +copied and compiled into your application. Files zahnrad.c and zahnrad.h make up +the core of the library, while stb_rect_pack.h and stb_truetype.h are +for a optional font handling implementation and can be removed if not needed. +- zahnrad.c +- zahnrad.h +- stb_rect_pack.h (optional) +- stb_truetype.h (optional) + +There are no dependencies or a particular building process required. You just have +to compile the .c file and #include zahnrad.h into your project. To actually +run you have to provide the input state, configuration style and memory +for draw commands to the library. After the GUI was executed all draw commands +have to be either executed or optionally converted into a vertex buffer to +draw the GUI. + +## Gallery +![screenshot](https://cloud.githubusercontent.com/assets/8057201/11761525/ae06f0ca-a0c6-11e5-819d-5610b25f6ef4.gif) +![demo](https://cloud.githubusercontent.com/assets/8057201/11282359/3325e3c6-8eff-11e5-86cb-cf02b0596087.png) +![node](https://cloud.githubusercontent.com/assets/8057201/9976995/e81ac04a-5ef7-11e5-872b-acd54fbeee03.gif) +![transparency](https://cloud.githubusercontent.com/assets/8057201/12779619/2a20d72c-ca69-11e5-95fe-4edecf820d5c.png) + +## Example +```c +/* init gui state */ +struct zr_context ctx; +zr_init_fixed(&ctx, calloc(1, MAX_MEMORY), MAX_MEMORY, &font); + +enum {EASY, HARD}; +int op = EASY; +float value = 0.6f; +int i = 20; + +struct zr_layout layout; +zr_begin(&ctx, &layout, "Show", zr_rect(50, 50, 220, 220), + ZR_WINDOW_BORDER|ZR_WINDOW_MOVEABLE|ZR_WINDOW_CLOSEABLE); +{ + /* fixed widget pixel width */ + zr_layout_row_static(&ctx, 30, 80, 1); + if (zr_button_text(&ctx, "button", ZR_BUTTON_DEFAULT)) { + /* event handling */ + } + + /* fixed widget window ratio width */ + zr_layout_row_dynamic(&ctx, 30, 2); + if (zr_option(&ctx, "easy", op == EASY)) op = EASY; + if (zr_option(&ctx, "hard", op == HARD)) op = HARD; + + /* custom widget pixel width */ + zr_layout_row_begin(&ctx, ZR_STATIC, 30, 2); + { + zr_layout_row_push(&ctx, 50); + zr_label(&ctx, "Volume:", ZR_TEXT_LEFT); + zr_layout_row_push(&ctx, 110); + zr_slider_float(&ctx, 0, &value, 1.0f, 0.1f); + } + zr_layout_row_end(&ctx); +} +zr_end(ctx); +``` +![example](https://cloud.githubusercontent.com/assets/8057201/10187981/584ecd68-675c-11e5-897c-822ef534a876.png) + diff --git a/deps/zahnrad/font/DroidSans.ttf b/deps/zahnrad/font/DroidSans.ttf new file mode 100644 index 0000000000000000000000000000000000000000..767c63ad000e3eea20f3cb7a43ba9f4154ed7a5d GIT binary patch literal 190044 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%0D=-PBu&{ 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 TYqd&VnqJLHIX9iq_T+y7#;O#c literal 0 HcmV?d00001 diff --git a/deps/zahnrad/font/Roboto-Light.ttf b/deps/zahnrad/font/Roboto-Light.ttf new file mode 100644 index 0000000000000000000000000000000000000000..664e1b2f9dbafbf6280305123d2df7fa0c66cee9 GIT binary patch literal 140276 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 literal 0 HcmV?d00001 diff --git a/deps/zahnrad/font/Roboto-Regular.ttf b/deps/zahnrad/font/Roboto-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3e6e2e76134cd6040a49ccf9961d302f7d139fcd GIT binary patch literal 145348 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 'width' */ +/* or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' */ +/* */ +/* If you don't do either of the above things, widths will be quantized to multiples */ +/* of small integers to guarantee the algorithm doesn't run out of temporary storage. */ +/* */ +/* If you do #2, then the non-quantized algorithm will be used, but the algorithm */ +/* may run out of temporary storage and be unable to pack some rectangles. */ + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +/* Optionally call this function after init but before doing any packing to */ +/* change the handling of the out-of-temp-memory scenario, described above. */ +/* If you call init again, this will be reset to the default (false). */ + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +/* Optionally select which packing heuristic the library should use. Different */ +/* heuristics will produce better/worse results for different data sets. */ +/* If you call init again, this will be reset to the default. */ + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* the details of the following structures don't matter to you, but they must */ +/* be visible so you can handle the memory allocations for them */ + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; /* we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' */ +}; + +#ifdef __cplusplus +} +#endif + +#endif + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* IMPLEMENTATION SECTION */ +/* */ + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#include + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + /* if it's ok to run out of memory, then don't bother aligning them; */ + /* this gives better packing, but may fail due to OOM (even though */ + /* the rectangles easily fit). @TODO a smarter approach would be to only */ + /* quantize once we've hit OOM, then we could get rid of this parameter. */ + context->align = 1; + else { + /* if it's not ok to run out of memory, then quantize the widths */ + /* so that num_nodes is always enough nodes. */ + /* */ + /* I.e. num_nodes * align >= width */ + /* align >= width / num_nodes */ + /* align = ceil(width/num_nodes) */ + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + /* node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) */ + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +/* find minimum y position if it starts at x1 */ +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + STBRP_ASSERT(first->x <= x0); + (void)c; + + #if 0 + /* skip in case we're past the node */ + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); /* we ended up handling this in the caller for efficiency */ + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + /* raise min_y higher. */ + /* we've accounted for all waste up to min_y, */ + /* but we'll now add more waste for everything we've visted */ + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + /* the first time through, visited_width might be reduced */ + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + /* add waste area */ + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + /* align to multiple of c->align */ + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { /* actually just want to test BL */ + /* bottom left */ + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + /* best-fit */ + if (y + height <= c->height) { + /* can only use it if it first vertically */ + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + /* if doing best-fit (BF), we also have to try aligning right edge to each node position */ + /* */ + /* e.g, if fitting */ + /* */ + /* ____________________ */ + /* |____________________| */ + /* */ + /* into */ + /* */ + /* | | */ + /* | ____________| */ + /* |____________| */ + /* */ + /* then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned */ + /* */ + /* This makes BF take about 2x the time */ + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + /* find first node that's admissible */ + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + /* find the left position that matches this */ + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + /* find best position according to heuristic */ + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + /* bail if: */ + /* 1. it failed */ + /* 2. the best node doesn't fit (we don't always check this) */ + /* 3. we're out of memory */ + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + /* on success, create new node */ + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + /* insert the new node into the right starting point, and */ + /* let 'cur' point to the remaining nodes needing to be */ + /* stiched back in */ + + cur = *res.prev_link; + if (cur->x < res.x) { + /* preserve the existing one, so start testing with the next one */ + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + /* from here, traverse cur and free the nodes, until we get to one */ + /* that shouldn't be freed */ + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + /* move the current node to the free list */ + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + /* stitch the list back in */ + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + stbrp_node *L1 = NULL, *L2 = NULL; + int count=0; + cur = context->active_head; + while (cur) { + L1 = cur; + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + L2 = cur; + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_width_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->w > q->w) + return -1; + if (p->w < q->w) + return 1; + return (p->h > q->h) ? -1 : (p->h < q->h); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i; + + /* we use the 'was_packed' field internally to allow sorting/unsorting */ + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + /* sort according to heuristic */ + qsort(rects, (size_t)num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + + /* unsort */ + qsort(rects, (size_t)num_rects, sizeof(rects[0]), rect_original_order); + + /* set was_packed flags */ + for (i=0; i < num_rects; ++i) + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); +} +#endif diff --git a/deps/zahnrad/stb_truetype.h b/deps/zahnrad/stb_truetype.h new file mode 100644 index 0000000000..a3cb2c5075 --- /dev/null +++ b/deps/zahnrad/stb_truetype.h @@ -0,0 +1,3218 @@ +/* stb_truetype.h - v1.07 - public domain */ +/* authored from 2009-2015 by Sean Barrett / RAD Game Tools */ +/* */ +/* This library processes TrueType files: */ +/* parse files */ +/* extract glyph metrics */ +/* extract glyph shapes */ +/* render glyphs to one-channel bitmaps with antialiasing (box filter) */ +/* */ +/* Todo: */ +/* non-MS cmaps */ +/* crashproof on bad data */ +/* hinting? (no longer patented) */ +/* cleartype-style AA? */ +/* optimize: use simple memory allocator for intermediates */ +/* optimize: build edge-list directly from curves */ +/* optimize: rasterize directly from curves? */ +/* */ +/* ADDITIONAL CONTRIBUTORS */ +/* */ +/* Mikko Mononen: compound shape support, more cmap formats */ +/* Tor Andersson: kerning, subpixel rendering */ +/* */ +/* Bug/warning reports/fixes: */ +/* "Zer" on mollyrocket (with fix) */ +/* Cass Everitt */ +/* stoiko (Haemimont Games) */ +/* Brian Hook */ +/* Walter van Niftrik */ +/* David Gow */ +/* David Given */ +/* Ivan-Assen Ivanov */ +/* Anthony Pesch */ +/* Johan Duparc */ +/* Hou Qiming */ +/* Fabian "ryg" Giesen */ +/* Martins Mozeiko */ +/* Cap Petschulat */ +/* Omar Cornut */ +/* github:aloucks */ +/* Peter LaValle */ +/* Giumo X. Clanjor */ +/* */ +/* Misc other: */ +/* Ryan Gordon */ +/* */ +/* VERSION HISTORY */ +/* */ +/* 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; */ +/* variant PackFontRanges to pack and render in separate phases; */ +/* fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); */ +/* fixed an assert() bug in the new rasterizer */ +/* replace assert() with STBTT_assert() in new rasterizer */ +/* 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) */ +/* also more precise AA rasterizer, except if shapes overlap */ +/* remove need for STBTT_sort */ +/* 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC */ +/* 1.04 (2015-04-15) typo in example */ +/* 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes */ +/* */ +/* Full history can be found at the end of this file. */ +/* */ +/* LICENSE */ +/* */ +/* This software is in the public domain. Where that dedication is not */ +/* recognized, you are granted a perpetual, irrevocable license to copy, */ +/* distribute, and modify this file as you see fit. */ +/* */ +/* USAGE */ +/* */ +/* Include this file in whatever places neeed to refer to it. In ONE C/C++ */ +/* file, write: */ +/* #define STB_TRUETYPE_IMPLEMENTATION */ +/* before the #include of this file. This expands out the actual */ +/* implementation into that C/C++ file. */ +/* */ +/* To make the implementation private to the file that generates the implementation, */ +/* #define STBTT_STATIC */ +/* */ +/* Simple 3D API (don't ship this, but it's fine for tools and quick start) */ +/* stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture */ +/* stbtt_GetBakedQuad() -- compute quad to draw for a given char */ +/* */ +/* Improved 3D API (more shippable): */ +/* #include "stb_rect_pack.h" -- optional, but you really want it */ +/* stbtt_PackBegin() */ +/* stbtt_PackSetOversample() -- for improved quality on small fonts */ +/* stbtt_PackFontRanges() -- pack and renders */ +/* stbtt_PackEnd() */ +/* stbtt_GetPackedQuad() */ +/* */ +/* "Load" a font file from a memory buffer (you have to keep the buffer loaded) */ +/* stbtt_InitFont() */ +/* stbtt_GetFontOffsetForIndex() -- use for TTC font collections */ +/* */ +/* Render a unicode codepoint to a bitmap */ +/* stbtt_GetCodepointBitmap() -- allocates and returns a bitmap */ +/* stbtt_MakeCodepointBitmap() -- renders into bitmap you provide */ +/* stbtt_GetCodepointBitmapBox() -- how big the bitmap must be */ +/* */ +/* Character advance/positioning */ +/* stbtt_GetCodepointHMetrics() */ +/* stbtt_GetFontVMetrics() */ +/* stbtt_GetCodepointKernAdvance() */ +/* */ +/* Starting with version 1.06, the rasterizer was replaced with a new, */ +/* faster and generally-more-precise rasterizer. The new rasterizer more */ +/* accurately measures pixel coverage for anti-aliasing, except in the case */ +/* where multiple shapes overlap, in which case it overestimates the AA pixel */ +/* coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If */ +/* this turns out to be a problem, you can re-enable the old rasterizer with */ +/* #define STBTT_RASTERIZER_VERSION 1 */ +/* which will incur about a 15% speed hit. */ +/* */ +/* ADDITIONAL DOCUMENTATION */ +/* */ +/* Immediately after this block comment are a series of sample programs. */ +/* */ +/* After the sample programs is the "header file" section. This section */ +/* includes documentation for each API function. */ +/* */ +/* Some important concepts to understand to use this library: */ +/* */ +/* Codepoint */ +/* Characters are defined by unicode codepoints, e.g. 65 is */ +/* uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is */ +/* the hiragana for "ma". */ +/* */ +/* Glyph */ +/* A visual character shape (every codepoint is rendered as */ +/* some glyph) */ +/* */ +/* Glyph index */ +/* A font-specific integer ID representing a glyph */ +/* */ +/* Baseline */ +/* Glyph shapes are defined relative to a baseline, which is the */ +/* bottom of uppercase characters. Characters extend both above */ +/* and below the baseline. */ +/* */ +/* Current Point */ +/* As you draw text to the screen, you keep track of a "current point" */ +/* which is the origin of each character. The current point's vertical */ +/* position is the baseline. Even "baked fonts" use this model. */ +/* */ +/* Vertical Font Metrics */ +/* The vertical qualities of the font, used to vertically position */ +/* and space the characters. See docs for stbtt_GetFontVMetrics. */ +/* */ +/* Font Size in Pixels or Points */ +/* The preferred interface for specifying font sizes in stb_truetype */ +/* is to specify how tall the font's vertical extent should be in pixels. */ +/* If that sounds good enough, skip the next paragraph. */ +/* */ +/* Most font APIs instead use "points", which are a common typographic */ +/* measurement for describing font size, defined as 72 points per inch. */ +/* stb_truetype provides a point API for compatibility. However, true */ +/* "per inch" conventions don't make much sense on computer displays */ +/* since they different monitors have different number of pixels per */ +/* inch. For example, Windows traditionally uses a convention that */ +/* there are 96 pixels per inch, thus making 'inch' measurements have */ +/* nothing to do with inches, and thus effectively defining a point to */ +/* be 1.333 pixels. Additionally, the TrueType font data provides */ +/* an explicit scale factor to scale a given font's glyphs to points, */ +/* but the author has observed that this scale factor is often wrong */ +/* for non-commercial fonts, thus making fonts scaled in points */ +/* according to the TrueType spec incoherently sized in practice. */ +/* */ +/* ADVANCED USAGE */ +/* */ +/* Quality: */ +/* */ +/* - Use the functions with Subpixel at the end to allow your characters */ +/* to have subpixel positioning. Since the font is anti-aliased, not */ +/* hinted, this is very import for quality. (This is not possible with */ +/* baked fonts.) */ +/* */ +/* - Kerning is now supported, and if you're supporting subpixel rendering */ +/* then kerning is worth using to give your text a polished look. */ +/* */ +/* Performance: */ +/* */ +/* - Convert Unicode codepoints to glyph indexes and operate on the glyphs; */ +/* if you don't do this, stb_truetype is forced to do the conversion on */ +/* every call. */ +/* */ +/* - There are a lot of memory allocations. We should modify it to take */ +/* a temp buffer and allocate from the temp buffer (without freeing), */ +/* should help performance a lot. */ +/* */ +/* NOTES */ +/* */ +/* The system uses the raw data found in the .ttf file without changing it */ +/* and without building auxiliary data structures. This is a bit inefficient */ +/* on little-endian systems (the data is big-endian), but assuming you're */ +/* caching the bitmaps or glyph shapes this shouldn't be a big deal. */ +/* */ +/* It appears to be very hard to programmatically determine what font a */ +/* given file is in a general way. I provide an API for this, but I don't */ +/* recommend it. */ +/* */ +/* */ +/* SOURCE STATISTICS (based on v0.6c, 2050 LOC) */ +/* */ +/* Documentation & header file 520 LOC \___ 660 LOC documentation */ +/* Sample code 140 LOC / */ +/* Truetype parsing 620 LOC ---- 620 LOC TrueType */ +/* Software rasterization 240 LOC \ . */ +/* Curve tesselation 120 LOC \__ 550 LOC Bitmap creation */ +/* Bitmap management 100 LOC / */ +/* Baked bitmap interface 70 LOC / */ +/* Font name matching & access 150 LOC ---- 150 */ +/* C runtime library abstraction 60 LOC ---- 60 */ +/* */ +/* */ +/* PERFORMANCE MEASUREMENTS FOR 1.06: */ +/* */ +/* 32-bit 64-bit */ +/* Previous release: 8.83 s 7.68 s */ +/* Pool allocations: 7.72 s 6.34 s */ +/* Inline sort : 6.54 s 5.65 s */ +/* New rasterizer : 5.63 s 5.00 s */ + +/*//////////////////////////////////////////////////////////////////////////// */ +/*//////////////////////////////////////////////////////////////////////////// */ +/*// */ +/*// SAMPLE PROGRAMS */ +/*// */ +/* */ +/* Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless */ +/* */ +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION /* force following include to generate implementation */ +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; /* ASCII 32..126 is 95 glyphs */ +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); /* no guarantee this fits! */ + /* can free ttf_buffer at this point */ + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + /* can free temp_bitmap at this point */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + /* assume orthographic projection with units = screen pixels, origin at top left */ + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);/*1=opengl & d3d10+,0=d3d9 */ + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +/* */ +/* */ +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Complete program (this compiles): get a single bitmap, print as ASCII art */ +/* */ +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION /* force following include to generate implementation */ +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +/* */ +/* Output: */ +/* */ +/* .ii. */ +/* @@@@@@. */ +/* V@Mio@@o */ +/* :i. V@V */ +/* :oM@@M */ +/* :@@@MM@M */ +/* @@o o@M */ +/* :@@. M@M */ +/* @@@o@@@@ */ +/* :M@@V:@@. */ +/* */ +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Complete program: print "Hello World!" banner, with bugs */ +/* */ +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; /* leave a little padding in case the character extends left */ + char *text = "Heljo World!"; /* intentionally misspelled to show 'lj' brokenness */ + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + /* note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong */ + /* because this API is really for baking character bitmaps into textures. if you want to render */ + /* a sequence of characters, you really need to render each bitmap to a temp buffer, then */ + /* "alpha blend" that into the working buffer */ + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + +/*//////////////////////////////////////////////////////////////////////////// */ +/*//////////////////////////////////////////////////////////////////////////// */ +/*// */ +/*// INTEGRATION WITH YOUR CODEBASE */ +/*// */ +/*// The following sections allow you to supply alternate definitions */ +/*// of C library functions used by stb_truetype. */ +#ifdef STB_TRUETYPE_IMPLEMENTATION + /* #define your own (u)stbtt_int8/16/32 before including to override this */ + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + /* #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h */ + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #endif + + /* #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h */ + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/*///////////////////////////////////////////////////////////////////////////// */ +/*///////////////////////////////////////////////////////////////////////////// */ +/*// */ +/*// INTERFACE */ +/*// */ +/*// */ +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* TEXTURE BAKING API */ +/* */ +/* If you use this API, you only have to call two functions ever. */ +/* */ + +typedef struct +{ + unsigned short x0,y0,x1,y1; /* coordinates of bbox in bitmap */ + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, /* font location (use offset=0 for plain .ttf) */ + float pixel_height, /* height of font in pixels */ + unsigned char *pixels, int pw, int ph, /* bitmap to be filled in */ + int first_char, int num_chars, /* characters to bake */ + stbtt_bakedchar *chardata); /* you allocate this, it's num_chars long */ +/* if return is positive, the first unused row of the bitmap */ +/* if return is negative, returns the negative of the number of characters that fit */ +/* if return is 0, no characters fit and no rows were used */ +/* This uses a very crappy packing. */ + +typedef struct +{ + float x0,y0,s0,t0; /* top-left */ + float x1,y1,s1,t1; /* bottom-right */ +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, /* same data as above */ + int char_index, /* character to display */ + float *xpos, float *ypos, /* pointers to current position in screen pixel space */ + stbtt_aligned_quad *q, /* output: quad to draw */ + int opengl_fillrule); /* true if opengl fill rule; false if DX9 or earlier */ +/* Call GetBakedQuad with char_index = 'character - first_char', and it */ +/* creates the quad you need to draw and advances the current position. */ +/* */ +/* The coordinate system used assumes y increases downwards. */ +/* */ +/* Characters will extend both above and below the current position; */ +/* see discussion of "BASELINE" above. */ +/* */ +/* It's inefficient; you might want to c&p it and optimize it. */ + + + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* NEW TEXTURE BAKING API */ +/* */ +/* This provides options for packing multiple fonts into one atlas, not */ +/* perfectly but better than nothing. */ + +typedef struct +{ + unsigned short x0,y0,x1,y1; /* coordinates of bbox in bitmap */ + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +/* Initializes a packing context stored in the passed-in stbtt_pack_context. */ +/* Future calls using this context will pack characters into the bitmap passed */ +/* in here: a 1-channel bitmap that is weight x height. stride_in_bytes is */ +/* the distance from one row to the next (or 0 to mean they are packed tightly */ +/* together). "padding" is the amount of padding to leave between each */ +/* character (normally you want '1' for bitmaps you'll use as textures with */ +/* bilinear filtering). */ +/* */ +/* Returns 0 on failure, 1 on success. */ + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +/* Cleans up the packing context and frees all memory. */ + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +/* Creates character bitmaps from the font_index'th font found in fontdata (use */ +/* font_index=0 if you don't know what that is). It creates num_chars_in_range */ +/* bitmaps for characters with unicode values starting at first_unicode_char_in_range */ +/* and increasing. Data for how to render them is stored in chardata_for_range; */ +/* pass these to stbtt_GetPackedQuad to get back renderable quads. */ +/* */ +/* font_size is the full height of the character from ascender to descender, */ +/* as computed by stbtt_ScaleForPixelHeight. To use a point size as computed */ +/* by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() */ +/* and pass that result as 'font_size': */ +/* ..., 20 , ... // font max minus min y is 20 pixels tall */ +/* ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall */ + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; /* if non-zero, then the chars are continuous, and this is the first codepoint */ + int *array_of_unicode_codepoints; /* if non-zero, then this is an array of unicode codepoints */ + int num_chars; + stbtt_packedchar *chardata_for_range; /* output */ + unsigned char h_oversample, v_oversample; /* don't set these, they're used internally */ +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +/* Creates character bitmaps from multiple ranges of characters stored in */ +/* ranges. This will usually create a better-packed bitmap than multiple */ +/* calls to stbtt_PackFontRange. Note that you can call this multiple */ +/* times within a single PackBegin/PackEnd. */ + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +/* Oversampling a font increases the quality by allowing higher-quality subpixel */ +/* positioning, and is especially valuable at smaller text sizes. */ +/* */ +/* This function sets the amount of oversampling for all following calls to */ +/* stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given */ +/* pack context. The default (no oversampling) is achieved by h_oversample=1 */ +/* and v_oversample=1. The total number of pixels required is */ +/* h_oversample*v_oversample larger than the default; for example, 2x2 */ +/* oversampling requires 4x the storage of 1x1. For best results, render */ +/* oversampled textures with bilinear filtering. Look at the readme in */ +/* stb/tests/oversample for information about oversampled fonts */ +/* */ +/* To use with PackFontRangesGather etc., you must set it before calls */ +/* call to PackFontRangesGatherRects. */ + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, /* same data as above */ + int char_index, /* character to display */ + float *xpos, float *ypos, /* pointers to current position in screen pixel space */ + stbtt_aligned_quad *q, /* output: quad to draw */ + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +/* Calling these functions in sequence is roughly equivalent to calling */ +/* stbtt_PackFontRanges(). If you more control over the packing of multiple */ +/* fonts, or if you want to pack custom data into a font texture, take a look */ +/* at the source to of stbtt_PackFontRanges() and create a custom version */ +/* using these functions, e.g. call GatherRects multiple times, */ +/* building up a single array of rects, then call PackRects once, */ +/* then call RenderIntoRects repeatedly. This may result in a */ +/* better packing than calling PackFontRanges multiple times */ +/* (or it may not). */ + +/* this is an opaque structure that you shouldn't mess with which holds */ +/* all the context needed from PackBegin to PackEnd. */ +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* FONT LOADING */ +/* */ +/* */ + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +/* Each .ttf/.ttc file may have more than one font. Each font has a sequential */ +/* index number starting from 0. Call this function to get the font offset for */ +/* a given index; it returns -1 if the index is out of range. A regular .ttf */ +/* file will only define one font and it always be at offset 0, so it will */ +/* return '0' for index 0, and -1 for all other indices. You can just skip */ +/* this step if you know it's that kind of font. */ + + +/* The following structure is defined publically so you can declare one on */ +/* the stack or as a global or etc, but you should treat it as opaque. */ +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; /* pointer to .ttf file */ + int fontstart; /* offset of start of font */ + + int numGlyphs; /* number of glyphs, needed for range checking */ + + int loca,head,glyf,hhea,hmtx,kern; /* table locations as offset from start of .ttf */ + int index_map; /* a cmap mapping for our chosen character encoding */ + int indexToLocFormat; /* format needed to map from glyph index to glyph */ +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +/* Given an offset into the file that defines a font, this function builds */ +/* the necessary cached info for the rest of the system. You must allocate */ +/* the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't */ +/* need to do anything special to free it, because the contents are pure */ +/* value data with no additional data structures. Returns 0 on failure. */ + + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* CHARACTER TO GLYPH-INDEX CONVERSIOn */ + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +/* If you're going to perform multiple operations on the same character */ +/* and you want a speed-up, call this function with the character you're */ +/* going to process, then use glyph-based functions instead of the */ +/* codepoint-based functions. */ + + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* CHARACTER PROPERTIES */ +/* */ + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +/* computes a scale factor to produce a font whose "height" is 'pixels' tall. */ +/* Height is measured as the distance from the highest ascender to the lowest */ +/* descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics */ +/* and computing: */ +/* scale = pixels / (ascent - descent) */ +/* so if you prefer to measure height by the ascent only, use a similar calculation. */ + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +/* computes a scale factor to produce a font whose EM size is mapped to */ +/* 'pixels' tall. This is probably what traditional APIs compute, but */ +/* I'm not positive. */ + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +/* ascent is the coordinate above the baseline the font extends; descent */ +/* is the coordinate below the baseline the font extends (i.e. it is typically negative) */ +/* lineGap is the spacing between one row's descent and the next row's ascent... */ +/* so you should advance the vertical position by "*ascent - *descent + *lineGap" */ +/* these are expressed in unscaled coordinates, so you must multiply by */ +/* the scale factor for a given size */ + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +/* the bounding box around all possible characters */ + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +/* leftSideBearing is the offset from the current horizontal position to the left edge of the character */ +/* advanceWidth is the offset from the current horizontal position to the next horizontal position */ +/* these are expressed in unscaled coordinates */ + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +/* an additional amount to add to the 'advance' value between ch1 and ch2 */ + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +/* Gets the bounding box of the visible part of the glyph, in unscaled coordinates */ + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +/* as above, but takes one or more glyph indices for greater efficiency */ + + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* GLYPH SHAPES (you probably don't need these, but they have to go before */ +/* the bitmaps for C declaration-order reasons) */ +/* */ + +#ifndef STBTT_vmove /* you can predefine these to use different values (but why?) */ + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex /* you can predefine this to use different values */ + /* (we share this with other code at RAD) */ + #define stbtt_vertex_type short /* can't use stbtt_int16 because that's not visible in the header file */ + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +/* returns non-zero if nothing is drawn for this glyph */ + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +/* returns # of vertices and fills *vertices with the pointer to them */ +/* these are expressed in "unscaled" coordinates */ +/* */ +/* The shape is a series of countours. Each one starts with */ +/* a STBTT_moveto, then consists of a series of mixed */ +/* STBTT_lineto and STBTT_curveto segments. A lineto */ +/* draws a line from previous endpoint to its x,y; a curveto */ +/* draws a quadratic bezier from previous endpoint to */ +/* its x,y, using cx,cy as the bezier control point. */ + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +/* frees the data allocated above */ + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* BITMAP RENDERING */ +/* */ + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +/* frees the bitmap allocated below */ + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +/* allocates a large-enough single-channel 8bpp bitmap and renders the */ +/* specified character/glyph at the specified scale into it, with */ +/* antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). */ +/* *width & *height are filled out with the width & height of the bitmap, */ +/* which is stored left-to-right, top-to-bottom. */ +/* */ +/* xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap */ + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +/* the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel */ +/* shift for the character */ + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +/* the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap */ +/* in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap */ +/* is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the */ +/* width and height and positioning info for it first. */ + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +/* same as stbtt_MakeCodepointBitmap, but you can specify a subpixel */ +/* shift for the character */ + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +/* get the bbox of the bitmap centered around the glyph origin; so the */ +/* bitmap width is ix1-ix0, height is iy1-iy0, and location to place */ +/* the bitmap top left is (leftSideBearing*scale,iy0). */ +/* (Note that the bitmap uses y-increases-down, but the shape uses */ +/* y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) */ + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +/* same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel */ +/* shift for the character */ + +/* the following functions are equivalent to the above functions, but operate */ +/* on glyph indices instead of Unicode codepoints (for efficiency) */ +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +/* @TODO: don't expose this structure */ +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Finding the right font... */ +/* */ +/* You should really just solve this offline, keep your own tables */ +/* of what font is what, and don't try to get it out of the .ttf file. */ +/* That's because getting it out of the .ttf file is really hard, because */ +/* the names in the file can appear in many possible encodings, in many */ +/* possible languages, and e.g. if you need a case-insensitive comparison, */ +/* the details of that depend on the encoding & language in a complex way */ +/* (actually underspecified in truetype, but also gigantic). */ +/* */ +/* But you can use the provided functions in two possible ways: */ +/* stbtt_FindMatchingFont() will use *case-sensitive* comparisons on */ +/* unicode-encoded names to try to find the font you want; */ +/* you can run this before calling stbtt_InitFont() */ +/* */ +/* stbtt_GetFontNameString() lets you get any of the various strings */ +/* from the file yourself and do your own comparisons on them. */ +/* You have to have called stbtt_InitFont() first. */ + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +/* returns the offset (not index) of the font that matches, or -1 if none */ +/* if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". */ +/* if you use any other flag, use a font name like "Arial"; this checks */ +/* the 'macStyle' header field; i don't know if fonts set this consistently */ +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 /* <= not same as 0, this makes us check the bitfield is 0 */ + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +/* returns 1/0 whether the first string interpreted as utf8 is identical to */ +/* the second string interpreted as big-endian utf16... useful for strings from next func */ + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +/* returns the string (which may be big-endian double byte, e.g. for unicode) */ +/* and puts the length in bytes in *length. */ +/* */ +/* some of the values for the IDs are below; for more see the truetype spec: */ +/* http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html */ +/* http://www.microsoft.com/typography/otspec/name.htm */ + +enum { /* platformID */ + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { /* encodingID for STBTT_PLATFORM_ID_UNICODE */ + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { /* encodingID for STBTT_PLATFORM_ID_MICROSOFT */ + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { /* encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes */ + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { /* languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... */ + /* problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs */ + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { /* languageID for STBTT_PLATFORM_ID_MAC */ + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __STB_INCLUDE_STB_TRUETYPE_H__ */ + +/*///////////////////////////////////////////////////////////////////////////// */ +/*///////////////////////////////////////////////////////////////////////////// */ +/*// */ +/*// IMPLEMENTATION */ +/*// */ +/*// */ + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +/*//////////////////////////////////////////////////////////////////////// */ +/* */ +/* accessors to parse data from file */ +/* */ + +/* on platforms that don't allow misaligned reads, if we want to allow */ +/* truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE */ + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + static stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + static stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + /* check the version number */ + if (stbtt_tag4(font, '1',0,0,0)) return 1; /* TrueType 1 */ + if (stbtt_tag(font, "typ1")) return 1; /* TrueType with type 1 font -- we don't support this! */ + if (stbtt_tag(font, "OTTO")) return 1; /* OpenType with CFF */ + if (stbtt_tag4(font, 0,1,0,0)) return 1; /* OpenType 1.0 */ + return 0; +} + +/* @OPTIMIZE: binary search */ +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + /* if it's just a font, there's only one valid index */ + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + /* check if it's a TTC */ + if (stbtt_tag(font_collection, "ttcf")) { + /* version 1? */ + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); /* required */ + info->loca = stbtt__find_table(data, fontstart, "loca"); /* required */ + info->head = stbtt__find_table(data, fontstart, "head"); /* required */ + info->glyf = stbtt__find_table(data, fontstart, "glyf"); /* required */ + info->hhea = stbtt__find_table(data, fontstart, "hhea"); /* required */ + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); /* required */ + info->kern = stbtt__find_table(data, fontstart, "kern"); /* not required */ + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + /* find a cmap encoding table we understand *now* to avoid searching */ + /* later. (todo: could make this installable) */ + /* the same regardless of glyph. */ + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + /* find an encoding we understand: */ + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + /* MS/Unicode */ + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + /* Mac/iOS has these */ + /* all the encodingIDs are unicode, so we don't bother to check it */ + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { /* apple byte encoding */ + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); /* @TODO: high-byte mapping for japanese/chinese/korean */ + return 0; + } else if (format == 4) { /* standard mapping for windows fonts: binary search collection of ranges */ + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + /* do a binary search of the segments */ + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + /* they lie from endCount .. endCount + segCount */ + /* but searchRange is the nearest power of two, so... */ + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + /* now decrement to bias correctly to find smallest */ + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + /* Binary search the right group. */ + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); /* rounds down, so low <= mid < high */ + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else /* format == 13 */ + return start_glyph; + } + } + return 0; /* not found */ + } + /* @TODO */ + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; /* glyph index out of range */ + if (info->indexToLocFormat >= 2) return -1; /* unknown index->glyph map format */ + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; /* if length is 0, return -1 */ +} + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; /* a loose bound on how many vertices we might need */ + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + /* in first pass, we load uninterpreted data into the allocated array */ + /* above, shifted to the end of the array so we won't overwrite it when */ + /* we create our final data starting from the front */ + + off = m - n; /* starting offset for uninterpreted data, regardless of how m ends up being calculated */ + + /* first load flags */ + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + /* now load x coordinates */ + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; /* ??? */ + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + /* now load y coordinates */ + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; /* ??? */ + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + /* now convert them to our format */ + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + /* now start the new one */ + start_off = !(flags & 1); + if (start_off) { + /* if we start off with an off-curve point, then when we need to find a point on the curve */ + /* where we can start, and we need to save some state for when we wraparound. */ + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + /* next point is also a curve point, so interpolate an on-point curve */ + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + /* otherwise just use the next point as our start point */ + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; /* we're using point i+1 as the starting point, so skip it */ + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { /* if it's a curve */ + if (was_off) /* two off-curve control points in a row means interpolate an on-curve midpoint */ + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + /* Compound shapes. */ + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { /* XY values */ + if (flags & 1) { /* shorts */ + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + /* @TODO handle matching point */ + STBTT_assert(0); + } + if (flags & (1<<3)) { /* WE_HAVE_A_SCALE */ + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { /* WE_HAVE_AN_X_AND_YSCALE */ + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { /* WE_HAVE_A_TWO_BY_TWO */ + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + /* Find transformation scales. */ + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + /* Get indexed glyph. */ + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + /* Transform vertices. */ + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + /* Append vertices. */ + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + /* More components ? */ + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + /* @TODO other compound variations? */ + STBTT_assert(0); + } else { + /* numberOfCounters == 0, do nothing */ + } + + *pvertices = vertices; + return num_vertices; +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + /* we only look at the first table. it must be 'horizontal' and format 0. */ + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) /* number of tables, need at least 1 */ + return 0; + if (ttUSHORT(data+8) != 1) /* horizontal flag must be set in format */ + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); /* note: unaligned read */ + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) /* if no kerning table, don't waste time looking up both codepoint->glyphs */ + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* antialiasing software rasterizer */ +/* */ + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + /* e.g. space character */ + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + /* move to integral bboxes (treating pixels as little squares, what pixels get touched)? */ + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* Rasterizer */ + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + if (!z) return z; + + /* round dx down to avoid overshooting */ + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); /* use z->dx so when we offset later it's by the same amount */ + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + /*STBTT_assert(e->y0 <= start_point); */ + if (!z) return z; + z->fdx = dxdy; + z->fdy = (1/dxdy); + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +/* note: this routine clips fills that extend off the edges... ideally this */ +/* wouldn't happen, but it could happen if the truetype glyph bounding boxes */ +/* are wrong, or if the user supplies a too-small bitmap */ +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + /* non-zero winding fill */ + int x0=0, w=0; + + while (e) { + if (w == 0) { + /* if we're currently at zero, we need to record the edge start point */ + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + /* if we went to zero, we need to draw */ + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + /* x0,x1 are the same pixel, so compute combined coverage */ + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) /* add antialiasing for x0 */ + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; /* clip */ + + if (j < len) /* add antialiasing for x1 */ + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; /* clip */ + + for (++i; i < j; ++i) /* fill pixels between x0 and x1 */ + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); /* weight per vertical scanline */ + int s; /* vertical subsample index */ + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + /* find center of pixel for this scanline */ + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + /* update all active edges; */ + /* remove all active edges that terminate before the center of this scanline */ + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; /* delete from list */ + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; /* advance to position for current scanline */ + step = &((*step)->next); /* advance through list */ + } + } + + /* resort the list if needed */ + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + /* insert all edges that start before the center of this scanline -- omit ones that also end on this scanline */ + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + /* find insertion point */ + if (active == NULL) + active = z; + else if (z->x < active->x) { + /* insert at front */ + z->next = active; + active = z; + } else { + /* find thing to insert AFTER */ + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + /* at this point, p->next->x is NOT < z->x */ + z->next = p->next; + p->next = z; + } + } + ++e; + } + + /* now process all active edges in XOR fashion */ + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +/* the edge passed in here does not cross the vertical line at x or the vertical line at x+1 */ +/* (i.e. it has already been clipped to those) */ +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); /* coverage = 1 - average x position */ + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + /* brute force every pixel */ + + /* compute intersection points with top & bottom */ + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float y0,y1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + /* compute endpoints of line segment clipped to this scanline (if the */ + /* line segment starts on this scanline. x0 is the intersection of the */ + /* line with y_top, but that may be off the line segment. */ + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + y0 = e->sy; + } else { + x_top = x0; + y0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + y1 = e->ey; + } else { + x_bottom = xb; + y1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + /* from here on, we don't have to range check x values */ + + if ((int) x_top == (int) x_bottom) { + float height; + /* simple case, only spans one pixel */ + int x = (int) x_top; + height = y1 - y0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; /* everything right of this pixel is filled */ + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + /* covers 2+ pixels */ + if (x_top > x_bottom) { + /* flip scanline vertically; signed area is the same */ + float t; + y0 = y_bottom - (y0 - y_top); + y1 = y_bottom - (y1 - y_top); + t = y0, y0 = y1, y1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + /* compute intersection with y axis at x1+1 */ + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + /* area of the rectangle covered from y0..y_crossing */ + area = sign * (y_crossing-y0); + /* area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) */ + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (y1-y_crossing); + + scanline_fill[x2] += sign * (y1-y0); + } + } else { + /* if edge goes outside of box we're drawing, we require */ + /* clipping logic. since this does not match the intended use */ + /* of this library, we use a different, very slow brute */ + /* force implementation */ + int x; + for (x=0; x < len; ++x) { + /* cases: */ + /* */ + /* there can be up to two intersections with the pixel. any intersection */ + /* with left or right edges can be handled by splitting into two (or three) */ + /* regions. intersections with top & bottom do not necessitate case-wise logic. */ + /* */ + /* the old way of doing this found the intersections with the left & right edges, */ + /* then used some simple logic to produce up to three segments in sorted order */ + /* from top-to-bottom. however, this had a problem: if an x edge was epsilon */ + /* across the x border, then the corresponding y position might not be distinct */ + /* from the other y segment, and it might ignored as an empty segment. to avoid */ + /* that, we need to explicitly produce segments based on x positions. */ + + /* rename variables to clear pairs */ + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + float y1,y2; + + /* x = e->x + e->dx * (y-y_top) */ + /* (y-y_top) = (x - e->x) / e->dx */ + /* y = (x - e->x) / e->dx + y_top */ + y1 = (x - x0) / dx + y_top; + y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { /* three segments descending down-right */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { /* three segments descending down-left */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { /* two segments across x, down-right */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { /* two segments across x, down-left */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { /* two segments across x+1, down-right */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { /* two segments across x+1, down-left */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { /* one segment */ + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +/* directly AA rasterize edges w/o supersampling */ +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + (void)vsubsample; + stbtt__hheap hh = { 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + /* find center of pixel for this scanline */ + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + /* update all active edges; */ + /* remove all active edges that terminate before the top of this scanline */ + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; /* delete from list */ + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); /* advance through list */ + } + } + + /* insert all edges that start before the bottom of this scanline */ + while (e->y0 <= scan_y_bottom) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + STBTT_assert(z->ey >= scan_y_top); + /* insert at front */ + z->next = active; + active = z; + ++e; + } + + /* now process all active edges */ + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + /* advance all the edges */ + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; /* advance to position for current scanline */ + step = &((*step)->next); /* advance through list */ + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + /* vsubsample should divide 255 evenly; otherwise we won't reach full opacity */ + + /* now we have to blow out the windings into explicit edge lists */ + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); /* add an extra one as a sentinel */ + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + /* skip the edge if horizontal */ + if (p[j].y == p[k].y) + continue; + /* add edge from j to k to the list */ + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + /* now sort the edges by their highest point (should snap to integer, and then by x) */ + /*STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); */ + stbtt__sort_edges(e, n); + + /* now, traverse the scanlines and find the intersections on each scanline, use xor winding rule */ + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; /* during first pass, it's unallocated */ + points[n].x = x; + points[n].y = y; +} + +/* tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching */ +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + /* midpoint */ + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + /* versus directly drawn line */ + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) /* 65536 segments on one curve better be enough! */ + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { /* half-pixel error allowed... need to be smaller if AA */ + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +/* returns number of contours */ +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + /* count how many "moves" there are to get the contour count */ + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + /* make two passes through the points so we don't need to realloc */ + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + /* start the next contour */ + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + /* now we get the size */ + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; /* in case we error */ + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* bitmap baking */ +/* */ +/* This is SUPER-CRAPPY packing to keep source code small */ + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, /* font location (use offset=0 for plain .ttf) */ + float pixel_height, /* height of font in pixels */ + unsigned char *pixels, int pw, int ph, /* bitmap to be filled in */ + int first_char, int num_chars, /* characters to bake */ + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); /* background of 0 around pixels */ + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; /* advance to next row */ + if (y + gh + 1 >= ph) /* check if it fits vertically AFTER potentially moving to next row */ + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* rectangle packing replacement routines if you don't have stb_rect_pack.h */ +/* */ + +#ifndef STB_RECT_PACK_VERSION +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +typedef int stbrp_coord; + +/*////////////////////////////////////////////////////////////////////////////////// */ +/* // */ +/* // */ +/* COMPILER WARNING ?!?!? // */ +/* // */ +/* // */ +/* if you get a compile warning due to these symbols being defined more than // */ +/* once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // */ +/* // */ +/*////////////////////////////////////////////////////////////////////////////////// */ + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* bitmap baking */ +/* */ +/* This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If */ +/* stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. */ + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); /* background of 0 around pixels */ + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + /* make kernel_width a constant in common cases so compiler can optimize out the divide */ + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + /* make kernel_width a constant in common cases so compiler can optimize out the divide */ + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + /* The prefilter is a box filter of width "oversample", */ + /* which shifts phase by (oversample - 1)/2 pixels in */ + /* oversampled space. We want to shift in the opposite */ + /* direction to counter this. */ + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +/* rects array must be big enough to accommodate all characters in the given ranges */ +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].first_unicode_codepoint_in_range ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +/* rects array must be big enough to accommodate all characters in the given ranges */ +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + /* save current values */ + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].first_unicode_codepoint_in_range ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + /* pad on left and top */ + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; /* if any fail, report failure */ + } + + ++k; + } + } + + /* restore original values */ + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + /*stbrp_context *context = (stbrp_context *) spc->pack_info; */ + stbrp_rect *rects; + + /* flag all characters as NOT packed */ + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +/*//////////////////////////////////////////////////////////////////////////// */ +/* */ +/* font name matching -- recommended not to use this */ +/* */ + +/* check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string */ +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + /* convert utf16 to utf8 and compare the results while converting */ + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; /* plus another 2 below */ + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +/* returns results in whatever encoding you request... but note that 2-byte encodings */ +/* will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare */ +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + /* find the encoding */ + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + /* is this a Unicode encoding? */ + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + /* check if there's a prefix match */ + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + /* check for target_id+1 immediately following, with same encoding & language */ + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + /* if nothing immediately following */ + if (matchlen == nlen) + return 1; + } + } + } + + /* @TODO handle other encodings */ + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + /* check italics/bold/underline flags in macStyle... */ + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + /* if we checked the macStyle flags, then just check the family and ignore the subfamily */ + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif /* STB_TRUETYPE_IMPLEMENTATION */ + + +/* FULL VERSION HISTORY */ +/* */ +/* 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; */ +/* allow PackFontRanges to pack and render in separate phases; */ +/* fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); */ +/* fixed an assert() bug in the new rasterizer */ +/* replace assert() with STBTT_assert() in new rasterizer */ +/* 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) */ +/* also more precise AA rasterizer, except if shapes overlap */ +/* remove need for STBTT_sort */ +/* 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC */ +/* 1.04 (2015-04-15) typo in example */ +/* 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes */ +/* 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ */ +/* 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match */ +/* non-oversampled; STBTT_POINT_SIZE for packed case only */ +/* 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling */ +/* 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) */ +/* 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID */ +/* 0.8b (2014-07-07) fix a warning */ +/* 0.8 (2014-05-25) fix a few more warnings */ +/* 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back */ +/* 0.6c (2012-07-24) improve documentation */ +/* 0.6b (2012-07-20) fix a few more warnings */ +/* 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, */ +/* stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty */ +/* 0.5 (2011-12-09) bugfixes: */ +/* subpixel glyph renderer computed wrong bounding box */ +/* first vertex of shape can be off-curve (FreeSans) */ +/* 0.4b (2011-12-03) fixed an error in the font baking example */ +/* 0.4 (2011-12-01) kerning, subpixel rendering (tor) */ +/* bugfixes for: */ +/* codepoint-to-glyph conversion using table fmt=12 */ +/* codepoint-to-glyph conversion using table fmt=4 */ +/* stbtt_GetBakedQuad with non-square texture (Zer) */ +/* updated Hello World! sample to use kerning and subpixel */ +/* fixed some warnings */ +/* 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) */ +/* userdata, malloc-from-userdata, non-zero fill (stb) */ +/* 0.2 (2009-03-11) Fix unsigned/signed char warnings */ +/* 0.1 (2009-03-09) First public release */ +/* */ diff --git a/deps/zahnrad/zahnrad.c b/deps/zahnrad/zahnrad.c new file mode 100644 index 0000000000..12245a1499 --- /dev/null +++ b/deps/zahnrad/zahnrad.c @@ -0,0 +1,11127 @@ +/* + Copyright (c) 2016 Micha Mettke + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "zahnrad.h" + +/* ============================================================== + * + * INTERNAL + * + * =============================================================== */ +#define ZR_POOL_DEFAULT_CAPACITY 16 +#define ZR_VALUE_PAGE_CAPACITY 32 +#define ZR_DEFAULT_COMMAND_BUFFER_SIZE (4*1024) + +enum zr_heading { + ZR_UP, + ZR_RIGHT, + ZR_DOWN, + ZR_LEFT +}; + +enum zr_draw_list_stroke { + ZR_STROKE_OPEN = zr_false, + /* build up path has no connection back to the beginning */ + ZR_STROKE_CLOSED = zr_true + /* build up path has a connection back to the beginning */ +}; + +struct zr_text_selection { + int active; + /* current selection state */ + zr_size begin; + /* text selection beginning glyph index */ + zr_size end; + /* text selection ending glyph index */ +}; + +struct zr_edit_box { + struct zr_buffer buffer; + /* glyph buffer to add text into */ + int active; + /* flag indicating if the buffer is currently being modified */ + zr_size cursor; + /* current glyph (not byte) cursor position */ + zr_size glyphs; + /* number of glyphs inside the edit box */ + struct zr_clipboard clip; + /* copy paste callbacks */ + zr_filter filter; + /* input filter callback */ + struct zr_text_selection sel; + /* text selection */ + float scrollbar; + /* edit field scrollbar */ + int text_inserted; +}; + +enum zr_internal_window_flags { + ZR_WINDOW_PRIVATE = ZR_FLAG(9), + /* dummy flag which mark the beginning of the private window flag part */ + ZR_WINDOW_ROM = ZR_FLAG(10), + /* sets the window into a read only mode and does not allow input changes */ + ZR_WINDOW_HIDDEN = ZR_FLAG(11), + /* Hiddes the window and stops any window interaction and drawing can be set + * by user input or by closing the window */ + ZR_WINDOW_MINIMIZED = ZR_FLAG(12), + /* marks the window as minimized */ + ZR_WINDOW_SUB = ZR_FLAG(13), + /* Marks the window as subwindow of another window*/ + ZR_WINDOW_GROUP = ZR_FLAG(14), + /* Marks the window as window widget group */ + ZR_WINDOW_POPUP = ZR_FLAG(15), + /* Marks the window as a popup window */ + ZR_WINDOW_NONBLOCK = ZR_FLAG(16), + /* Marks the window as a nonblock popup window */ + ZR_WINDOW_CONTEXTUAL = ZR_FLAG(17), + /* Marks the window as a combo box or menu */ + ZR_WINDOW_COMBO = ZR_FLAG(18), + /* Marks the window as a combo box */ + ZR_WINDOW_MENU = ZR_FLAG(19), + /* Marks the window as a menu */ + ZR_WINDOW_TOOLTIP = ZR_FLAG(20), + /* Marks the window as a menu */ + ZR_WINDOW_REMOVE_ROM = ZR_FLAG(21) + /* Removes the read only mode at the end of the window */ +}; + +struct zr_popup { + struct zr_window *win; + enum zr_internal_window_flags type; + zr_hash name; + int active; + + unsigned combo_count; + unsigned con_count, con_old; + unsigned active_con; +}; + +struct zr_edit_state { + zr_hash name; + zr_size cursor; + struct zr_text_selection sel; + float scrollbar; + unsigned int seq; + unsigned int old; + int active, prev; +}; + +struct zr_value { + int active, prev; + char buffer[ZR_MAX_NUMBER_BUFFER]; + zr_size length; + zr_size cursor; + zr_hash name; + unsigned int seq; + unsigned int old; + int state; +}; + +struct zr_table { + unsigned int seq; + zr_hash keys[ZR_VALUE_PAGE_CAPACITY]; + zr_uint values[ZR_VALUE_PAGE_CAPACITY]; + struct zr_table *next, *prev; +}; + +struct zr_window { + zr_hash name; + /* name of this window */ + unsigned int seq; + /* window lifeline */ + struct zr_rect bounds; + /* window size and position */ + zr_flags flags; + /* window flags modifing its behavior */ + struct zr_scroll scrollbar; + /* scrollbar x- and y-offset */ + struct zr_command_buffer buffer; + /* command buffer for queuing drawing calls */ + + /* frame window state */ + struct zr_panel *layout; + + /* persistent widget state */ + struct zr_value property; + struct zr_popup popup; + struct zr_edit_state edit; + + struct zr_table *tables; + unsigned short table_count; + unsigned short table_size; + + /* window list */ + struct zr_window *next; + struct zr_window *prev; + struct zr_window *parent; +}; + +union zr_page_data { + struct zr_table tbl; + struct zr_window win; +}; + +struct zr_window_page { + unsigned size; + struct zr_window_page *next; + union zr_page_data win[1]; +}; + +struct zr_pool { + struct zr_allocator alloc; + enum zr_allocation_type type; + unsigned int page_count; + struct zr_window_page *pages; + unsigned capacity; + zr_size size; + zr_size cap; +}; + +/* ============================================================== + * MATH + * =============================================================== */ +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) < (b) ? (b) : (a)) +#endif +#ifndef CLAMP +#define CLAMP(i,v,x) (MAX(MIN(v,x), i)) +#endif + +#define ZR_PI 3.141592654f +#define ZR_UTF_INVALID 0xFFFD +#define ZR_MAX_FLOAT_PRECISION 2 + +#define ZR_UNUSED(x) ((void)(x)) +#define ZR_SATURATE(x) (MAX(0, MIN(1.0f, x))) +#define ZR_LEN(a) (sizeof(a)/sizeof(a)[0]) +#define ZR_ABS(a) (((a) < 0) ? -(a) : (a)) +#define ZR_BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define ZR_INBOX(px, py, x, y, w, h)\ + (ZR_BETWEEN(px,x,x+w) && ZR_BETWEEN(py,y,y+h)) +#define ZR_INTERSECT(x0, y0, w0, h0, x1, y1, w1, h1) \ + (!(((x1 > (x0 + w0)) || ((x1 + w1) < x0) || (y1 > (y0 + h0)) || (y1 + h1) < y0))) +#define ZR_CONTAINS(x, y, w, h, bx, by, bw, bh)\ + (ZR_INBOX(x,y, bx, by, bw, bh) && ZR_INBOX(x+w,y+h, bx, by, bw, bh)) + +#define zr_vec2_mov(to,from) (to).x = (from).x, (to).y = (from).y +#define zr_vec2_sub(a, b) zr_vec2((a).x - (b).x, (a).y - (b).y) +#define zr_vec2_add(a, b) zr_vec2((a).x + (b).x, (a).y + (b).y) +#define zr_vec2_len_sqr(a) ((a).x*(a).x+(a).y*(a).y) +#define zr_vec2_muls(a, t) zr_vec2((a).x * (t), (a).y * (t)) + +#define zr_ptr_add(t, p, i) ((t*)((void*)((zr_byte*)(p) + (i)))) +#define zr_ptr_add_const(t, p, i) ((const t*)((const void*)((const zr_byte*)(p) + (i)))) + +static const struct zr_rect zr_null_rect = {-8192.0f, -8192.0f, 16384, 16384}; +static const float FLOAT_PRECISION = 0.00000000000001f; + +/* ============================================================== + * ALIGNMENT + * =============================================================== */ +/* Pointer to Integer type conversion for pointer alignment */ +#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC*/ +# define ZR_UINT_TO_PTR(x) ((void*)(__PTRDIFF_TYPE__)(x)) +# define ZR_PTR_TO_UINT(x) ((zr_size)(__PTRDIFF_TYPE__)(x)) +#elif !defined(__GNUC__) /* works for compilers other than LLVM */ +# define ZR_UINT_TO_PTR(x) ((void*)&((char*)0)[x]) +# define ZR_PTR_TO_UINT(x) ((zr_size)(((char*)x)-(char*)0)) +#elif defined(ZR_USE_FIXED_TYPES) /* used if we have */ +# define ZR_UINT_TO_PTR(x) ((void*)(uintptr_t)(x)) +# define ZR_PTR_TO_UINT(x) ((uintptr_t)(x)) +#else /* generates warning but works */ +# define ZR_UINT_TO_PTR(x) ((void*)(x)) +# define ZR_PTR_TO_UINT(x) ((zr_size)(x)) +#endif + +#define ZR_ALIGN_PTR(x, mask)\ + (ZR_UINT_TO_PTR((ZR_PTR_TO_UINT((zr_byte*)(x) + (mask-1)) & ~(mask-1)))) +#define ZR_ALIGN_PTR_BACK(x, mask)\ + (ZR_UINT_TO_PTR((ZR_PTR_TO_UINT((zr_byte*)(x)) & ~(mask-1)))) + +#ifdef __cplusplus +template struct zr_alignof; +template struct zr_helper{enum {value = size_diff};}; +template struct zr_helper{enum {value = zr_alignof::value};}; +template struct zr_alignof{struct Big {T x; char c;}; enum { + diff = sizeof(Big) - sizeof(T), value = zr_helper::value};}; +#define ZR_ALIGNOF(t) (zr_alignof::value); +#else +#define ZR_ALIGNOF(t) ((char*)(&((struct {char c; t _h;}*)0)->_h) - (char*)0) +#endif + +/* make sure correct type size */ +typedef int zr__check_size[(sizeof(zr_size) >= sizeof(void*)) ? 1 : -1]; +typedef int zr__check_ptr[(sizeof(zr_ptr) == sizeof(void*)) ? 1 : -1]; +typedef int zr__check_flags[(sizeof(zr_flags) >= 4) ? 1 : -1]; +typedef int zr__check_rune[(sizeof(zr_rune) >= 4) ? 1 : -1]; +typedef int zr__check_uint[(sizeof(zr_uint) == 4) ? 1 : -1]; +typedef int zr__check_byte[(sizeof(zr_byte) == 1) ? 1 : -1]; +/* + * ============================================================== + * + * MATH + * + * =============================================================== + */ +/* Since zahnrad is supposed to work on all systems providing floating point + math without any dependencies I also had to implement my own math functions + for sqrt, sin and cos. Since the actual highly accurate implementations for + the standard library functions are quite complex and I do not need high + precision for my use cases I use approximations. + + Sqrt + ---- + For square root zahnrad uses the famous fast inverse square root: + https://en.wikipedia.org/wiki/Fast_inverse_square_root with + slightly tweaked magic constant. While on todays hardware it is + probably not faster it is still fast and accurate enough for + zahnrads use cases. + + Sine/Cosine + ----------- + All constants inside both function are generated Remez's minimax + approximations for value range 0...2*PI. The reason why I decided to + approximate exactly that range is that zahnrad only needs sine and + cosine to generate circles which only requires that exact range. + In addition I used Remez instead of Taylor for additional precision: + www.lolengine.net/blog/2011/12/21/better-function-approximatations. + + The tool I used to generate constants for both sine and cosine + (it can actually approximate a lot more functions) can be + found here: www.lolengine.net/wiki/oss/lolremez +*/ +static float +zr_inv_sqrt(float number) +{ + float x2; + const float threehalfs = 1.5f; + union {zr_uint i; float f;} conv = {0}; + conv.f = number; + x2 = number * 0.5f; + conv.i = 0x5f375A84 - (conv.i >> 1); + conv.f = conv.f * (threehalfs - (x2 * conv.f * conv.f)); + return conv.f; +} + +static float +zr_sin(float x) +{ + static const float a0 = +1.91059300966915117e-31f; + static const float a1 = +1.00086760103908896f; + static const float a2 = -1.21276126894734565e-2f; + static const float a3 = -1.38078780785773762e-1f; + static const float a4 = -2.67353392911981221e-2f; + static const float a5 = +2.08026600266304389e-2f; + static const float a6 = -3.03996055049204407e-3f; + static const float a7 = +1.38235642404333740e-4f; + return a0 + x*(a1 + x*(a2 + x*(a3 + x*(a4 + x*(a5 + x*(a6 + x*a7)))))); +} + +static float +zr_cos(float x) +{ + static const float a0 = +1.00238601909309722f; + static const float a1 = -3.81919947353040024e-2f; + static const float a2 = -3.94382342128062756e-1f; + static const float a3 = -1.18134036025221444e-1f; + static const float a4 = +1.07123798512170878e-1f; + static const float a5 = -1.86637164165180873e-2f; + static const float a6 = +9.90140908664079833e-4f; + static const float a7 = -5.23022132118824778e-14f; + return a0 + x*(a1 + x*(a2 + x*(a3 + x*(a4 + x*(a5 + x*(a6 + x*a7)))))); +} + +static zr_uint +zr_round_up_pow2(zr_uint v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +struct zr_rect +zr_get_null_rect(void) +{ + return zr_null_rect; +} + +struct zr_rect +zr_rect(float x, float y, float w, float h) +{ + struct zr_rect r; + r.x = x, r.y = y; + r.w = w, r.h = h; + return r; +} + +static struct zr_rect +zr_shrink_rect(struct zr_rect r, float amount) +{ + struct zr_rect res; + r.w = MAX(r.w, 2 * amount); + r.h = MAX(r.h, 2 * amount); + res.x = r.x + amount; + res.y = r.y + amount; + res.w = r.w - 2 * amount; + res.h = r.h - 2 * amount; + return res; +} + +static struct zr_rect +zr_pad_rect(struct zr_rect r, struct zr_vec2 pad) +{ + r.w = MAX(r.w, 2 * pad.x); + r.h = MAX(r.h, 2 * pad.y); + r.x += pad.x; r.y += pad.y; + r.w -= 2 * pad.x; + r.h -= 2 * pad.y; + return r; +} + +struct zr_vec2 +zr_vec2(float x, float y) +{ + struct zr_vec2 ret; + ret.x = x; ret.y = y; + return ret; +} +/* + * ============================================================== + * + * STANDARD + * + * =============================================================== + */ +static int zr_str_match_here(const char *regexp, const char *text); +static int zr_str_match_star(int c, const char *regexp, const char *text); + +static void* +zr_memcopy(void *dst0, const void *src0, zr_size length) +{ + zr_ptr t; + typedef int word; + char *dst = (char*)dst0; + const char *src = (const char*)src0; + if (length == 0 || dst == src) + goto done; + + #define wsize sizeof(word) + #define wmask (wsize-1) + #define TLOOP(s) if (t) TLOOP1(s) + #define TLOOP1(s) do { s; } while (--t) + + if (dst < src) { + t = (zr_ptr)src; /* only need low bits */ + if ((t | (zr_ptr)dst) & wmask) { + if ((t ^ (zr_ptr)dst) & wmask || length < wsize) + t = length; + else + t = wsize - (t & wmask); + length -= t; + TLOOP1(*dst++ = *src++); + } + t = length / wsize; + TLOOP(*(word*)(void*)dst = *(const word*)(const void*)src; + src += wsize; dst += wsize); + t = length & wmask; + TLOOP(*dst++ = *src++); + } else { + src += length; + dst += length; + t = (zr_ptr)src; + if ((t | (zr_ptr)dst) & wmask) { + if ((t ^ (zr_ptr)dst) & wmask || length <= wsize) + t = length; + else + t &= wmask; + length -= t; + TLOOP1(*--dst = *--src); + } + t = length / wsize; + TLOOP(src -= wsize; dst -= wsize; + *(word*)(void*)dst = *(const word*)(const void*)src); + t = length & wmask; + TLOOP(*--dst = *--src); + } + #undef wsize + #undef wmask + #undef TLOOP + #undef TLOOP1 +done: + return (dst0); +} + +static void +zr_memset(void *ptr, int c0, zr_size size) +{ + #define word unsigned + #define wsize sizeof(word) + #define wmask (wsize - 1) + zr_byte *dst = (zr_byte*)ptr; + unsigned c = 0; + zr_size t = 0; + + if ((c = (zr_byte)c0) != 0) { + c = (c << 8) | c; /* at least 16-bits */ + if (sizeof(unsigned int) > 2) + c = (c << 16) | c; /* at least 32-bits*/ + } + + /* to small of a word count */ + dst = (zr_byte*)ptr; + if (size < 3 * wsize) { + while (size--) *dst++ = (zr_byte)c0; + return; + } + + /* align destination */ + if ((t = ZR_PTR_TO_UINT(dst) & wmask) != 0) { + t = wsize -t; + size -= t; + do { + *dst++ = (zr_byte)c0; + } while (--t != 0); + } + + /* fill word */ + t = size / wsize; + do { + *(word*)((void*)dst) = c; + dst += wsize; + } while (--t != 0); + + /* fill trailing bytes */ + t = (size & wmask); + if (t != 0) { + do { + *dst++ = (zr_byte)c0; + } while (--t != 0); + } + + #undef word + #undef wsize + #undef wmask +} + +#define zr_zero_struct(s) zr_zero(&s, sizeof(s)) + +static void +zr_zero(void *ptr, zr_size size) +{ + ZR_ASSERT(ptr); + zr_memset(ptr, 0, size); +} + +static zr_size +zr_strsiz(const char *str) +{ + zr_size siz = 0; + ZR_ASSERT(str); + while (str && *str++ != '\0') siz++; + return siz; +} + +static int +zr_strtof(float *number, const char *buffer) +{ + float m; + float neg = 1.0f; + const char *p = buffer; + float floatvalue = 0; + + ZR_ASSERT(number); + ZR_ASSERT(buffer); + if (!number || !buffer) return 0; + *number = 0; + + /* skip whitespace */ + while (*p && *p == ' ') p++; + if (*p == '-') { + neg = -1.0f; + p++; + } + + while( *p && *p != '.' && *p != 'e' ) { + floatvalue = floatvalue * 10.0f + (float) (*p - '0'); + p++; + } + + if ( *p == '.' ) { + p++; + for(m = 0.1f; *p && *p != 'e'; p++ ) { + floatvalue = floatvalue + (float) (*p - '0') * m; + m *= 0.1f; + } + } + if ( *p == 'e' ) { + int i, pow, div; + p++; + if ( *p == '-' ) { + div = zr_true; + p++; + } else if ( *p == '+' ) { + div = zr_false; + p++; + } else div = zr_false; + + for ( pow = 0; *p; p++ ) + pow = pow * 10 + (int) (*p - '0'); + + for ( m = 1.0, i = 0; i < pow; i++ ) + m *= 10.0f; + + if ( div ) + floatvalue /= m; + else floatvalue *= m; + } + *number = floatvalue * neg; + return 1; +} + +static int +zr_str_match_here(const char *regexp, const char *text) +{ + if (regexp[0] == '\0') + return 1; + if (regexp[1] == '*') + return zr_str_match_star(regexp[0], regexp+2, text); + if (regexp[0] == '$' && regexp[1] == '\0') + return *text == '\0'; + if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text)) + return zr_str_match_here(regexp+1, text+1); + return 0; +} + +static int +zr_str_match_star(int c, const char *regexp, const char *text) +{ + do {/* a '* matches zero or more instances */ + if (zr_str_match_here(regexp, text)) + return 1; + } while (*text != '\0' && (*text++ == c || c == '.')); + return 0; +} + +static int +zr_strfilter(const char *text, const char *regexp) +{ + /* + c matches any literal character c + . matches any single character + ^ matches the beginning of the input string + $ matches the end of the input string + * matches zero or more occurrences of the previous character*/ + if (regexp[0] == '^') + return zr_str_match_here(regexp+1, text); + do { /* must look even if string is empty */ + if (zr_str_match_here(regexp, text)) + return 1; + } while (*text++ != '\0'); + return 0; +} + +static float +zr_pow(float x, int n) +{ + /* check the sign of n */ + float r = 1; + int plus = n >= 0; + n = (plus) ? n : -n; + while (n > 0) { + if ((n & 1) == 1) + r *= x; + n /= 2; + x *= x; + } + return plus ? r : 1.0f / r; +} + +static float +zr_floor(float x) +{ + return (float)((int)x - ((x < 0.0) ? 1 : 0)); +} + +static int +zr_log10(float n) +{ + int neg; + int ret; + int exp = 0; + + neg = (n < 0) ? 1 : 0; + ret = (neg) ? (int)-n : (int)n; + while ((ret / 10) > 0) { + ret /= 10; + exp++; + } + if (neg) exp = -exp; + return exp; +} + +static zr_size +zr_ftos(char *s, float n) +{ + int useExp = 0; + int digit = 0, m = 0, m1 = 0; + char *c = s; + int neg = 0; + + if (n == 0.0) { + s[0] = '0'; s[1] = '\0'; + return 1; + } + + neg = (n < 0); + if (neg) n = -n; + + /* calculate magnitude */ + m = zr_log10(n); + useExp = (m >= 14 || (neg && m >= 9) || m <= -9); + if (neg) *(c++) = '-'; + + /* set up for scientific notation */ + if (useExp) { + if (m < 0) + m -= 1; + n = n / zr_pow(10.0, m); + m1 = m; + m = 0; + } + if (m < 1.0) { + m = 0; + } + + /* convert the number */ + while (n > FLOAT_PRECISION || m >= 0) { + float weight = zr_pow(10.0, m); + if (weight > 0) { + float t = (float)n / weight; + float tmp = zr_floor(t); + digit = (int)tmp; + n -= ((float)digit * weight); + *(c++) = (char)('0' + (char)digit); + } + if (m == 0 && n > 0) + *(c++) = '.'; + m--; + } + + if (useExp) { + /* convert the exponent */ + int i, j; + *(c++) = 'e'; + if (m1 > 0) { + *(c++) = '+'; + } else { + *(c++) = '-'; + m1 = -m1; + } + m = 0; + while (m1 > 0) { + *(c++) = (char)('0' + (char)(m1 % 10)); + m1 /= 10; + m++; + } + c -= m; + for (i = 0, j = m-1; i> (32 - r))) + union {const zr_uint *i; const zr_byte *b;} conv = {0}; + const zr_byte *data = (const zr_byte*)key; + const int nblocks = len/4; + zr_uint h1 = seed; + const zr_uint c1 = 0xcc9e2d51; + const zr_uint c2 = 0x1b873593; + const zr_byte *tail; + const zr_uint *blocks; + zr_uint k1; + int i; + + /* body */ + if (!key) return 0; + conv.b = (data + nblocks*4); + blocks = (const zr_uint*)conv.i; + for (i = -nblocks; i; ++i) { + k1 = blocks[i]; + k1 *= c1; + k1 = ZR_ROTL(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = ZR_ROTL(h1,13); + h1 = h1*5+0xe6546b64; + } + + /* tail */ + tail = (const zr_byte*)(data + nblocks*4); + k1 = 0; + switch (len & 3) { + case 3: k1 ^= (zr_uint)(tail[2] << 16); + case 2: k1 ^= (zr_uint)(tail[1] << 8u); + case 1: k1 ^= tail[0]; + k1 *= c1; + k1 = ZR_ROTL(k1,15); + k1 *= c2; + h1 ^= k1; + default: break; + } + + /* finalization */ + h1 ^= (zr_uint)len; + /* fmix32 */ + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + #undef ZR_ROTL + return h1; +} + +/* + * ============================================================== + * + * COLOR + * + * =============================================================== + */ +struct zr_color +zr_rgba(zr_byte r, zr_byte g, zr_byte b, zr_byte a) +{ + struct zr_color ret; + ret.r = r; ret.g = g; + ret.b = b; ret.a = a; + return ret; +} + +struct zr_color +zr_rgb(zr_byte r, zr_byte g, zr_byte b) +{ + struct zr_color ret; + ret.r = r; ret.g = g; + ret.b = b; ret.a = 255; + return ret; +} + +struct zr_color +zr_rgba32(zr_uint in) +{ + struct zr_color ret; + ret.r = (in & 0xFF); + ret.g = ((in >> 8) & 0xFF); + ret.b = ((in >> 16) & 0xFF); + ret.a = (zr_byte)((in >> 24) & 0xFF); + return ret; +} + +struct zr_color +zr_rgba_f(float r, float g, float b, float a) +{ + struct zr_color ret; + ret.r = (zr_byte)(ZR_SATURATE(r) * 255.0f); + ret.g = (zr_byte)(ZR_SATURATE(g) * 255.0f); + ret.b = (zr_byte)(ZR_SATURATE(b) * 255.0f); + ret.a = (zr_byte)(ZR_SATURATE(a) * 255.0f); + return ret; +} + +struct zr_color +zr_rgb_f(float r, float g, float b) +{ + struct zr_color ret; + ret.r = (zr_byte)(ZR_SATURATE(r) * 255.0f); + ret.g = (zr_byte)(ZR_SATURATE(g) * 255.0f); + ret.b = (zr_byte)(ZR_SATURATE(b) * 255.0f); + ret.a = 255; + return ret; +} + +struct zr_color +zr_hsv(zr_byte h, zr_byte s, zr_byte v) +{ + return zr_hsva(h, s, v, 255); +} + +struct zr_color +zr_hsv_f(float h, float s, float v) +{ + return zr_hsva_f(h, s, v, 1.0f); +} + +struct zr_color +zr_hsva(zr_byte h, zr_byte s, zr_byte v, zr_byte a) +{ + float hf = (float)h / 255.0f; + float sf = (float)s / 255.0f; + float vf = (float)v / 255.0f; + float af = (float)a / 255.0f; + return zr_hsva_f(hf, sf, vf, af); +} + +struct zr_color +zr_hsva_f(float h, float s, float v, float a) +{ + struct zr_colorf {float r,g,b;} out = {0,0,0}; + float hh, p, q, t, ff; + zr_uint i; + + if (s <= 0.0f) { + out.r = v; out.g = v; out.b = v; + return zr_rgb_f(out.r, out.g, out.b); + } + + hh = h; + if (hh >= 360.0f) hh = 0; + hh /= 60.0f; + i = (zr_uint)hh; + ff = hh - (float)i; + p = v * (1.0f - s); + q = v * (1.0f - (s * ff)); + t = v * (1.0f - (s * (1.0f - ff))); + + switch (i) { + case 0: + out.r = v; + out.g = t; + out.b = p; + break; + case 1: + out.r = q; + out.g = v; + out.b = p; + break; + case 2: + out.r = p; + out.g = v; + out.b = t; + break; + case 3: + out.r = p; + out.g = q; + out.b = v; + break; + case 4: + out.r = t; + out.g = p; + out.b = v; + break; + case 5: + default: + out.r = v; + out.g = p; + out.b = q; + break; + } + return zr_rgba_f(out.r, out.g, out.b, a); +} + +zr_uint +zr_color32(struct zr_color in) +{ + zr_uint out = (zr_uint)in.r; + out |= ((zr_uint)in.g << 8); + out |= ((zr_uint)in.b << 16); + out |= ((zr_uint)in.a << 24); + return out; +} + +void +zr_colorf(float *r, float *g, float *b, float *a, struct zr_color in) +{ + static const float s = 1.0f/255.0f; + *r = (float)in.r * s; + *g = (float)in.g * s; + *b = (float)in.b * s; + *a = (float)in.a * s; +} + +void +zr_color_hsv_f(float *out_h, float *out_s, float *out_v, struct zr_color in) +{ + float a; + zr_color_hsva_f(out_h, out_s, out_v, &a, in); +} + +void +zr_color_hsva_f(float *out_h, float *out_s, + float *out_v, float *out_a, struct zr_color in) +{ + float chroma; + float K = 0.0f; + float r,g,b,a; + + zr_colorf(&r,&g,&b,&a, in); + if (g < b) { + const float t = g; g = b; b = t; + K = -1.f; + } + if (a < g) { + const float t = r; r = g; g = t; + K = -2.f/6.0f - K; + } + chroma = r - ((g < b) ? g: b); + *out_h = ZR_ABS(K + (g - b)/(6.0f * chroma + 1e-20f)); + *out_s = chroma / (r + 1e-20f); + *out_v = r; + *out_a = (float)in.a / 255.0f; +} + +void +zr_color_hsva(int *out_h, int *out_s, int *out_v, + int *out_a, struct zr_color in) +{ + float h,s,v,a; + zr_color_hsva_f(&h, &s, &v, &a, in); + *out_h = (zr_byte)(h * 255.0f); + *out_s = (zr_byte)(s * 255.0f); + *out_v = (zr_byte)(v * 255.0f); + *out_a = (zr_byte)(a * 255.0f); +} + +void +zr_color_hsv(int *out_h, int *out_s, int *out_v, struct zr_color in) +{ + int a; + zr_color_hsva(out_h, out_s, out_v, &a, in); +} +/* + * ============================================================== + * + * IMAGE + * + * =============================================================== + */ +zr_handle +zr_handle_ptr(void *ptr) +{ + zr_handle handle = {0}; + handle.ptr = ptr; + return handle; +} + +zr_handle +zr_handle_id(int id) +{ + zr_handle handle; + handle.id = id; + return handle; +} + +struct zr_image +zr_subimage_ptr(void *ptr, unsigned short w, unsigned short h, struct zr_rect r) +{ + struct zr_image s; + zr_zero(&s, sizeof(s)); + s.handle.ptr = ptr; + s.w = w; s.h = h; + s.region[0] = (unsigned short)r.x; + s.region[1] = (unsigned short)r.y; + s.region[2] = (unsigned short)r.w; + s.region[3] = (unsigned short)r.h; + return s; +} + +struct zr_image +zr_subimage_id(int id, unsigned short w, unsigned short h, struct zr_rect r) +{ + struct zr_image s; + zr_zero(&s, sizeof(s)); + s.handle.id = id; + s.w = w; s.h = h; + s.region[0] = (unsigned short)r.x; + s.region[1] = (unsigned short)r.y; + s.region[2] = (unsigned short)r.w; + s.region[3] = (unsigned short)r.h; + return s; +} + +struct zr_image +zr_image_ptr(void *ptr) +{ + struct zr_image s; + zr_zero(&s, sizeof(s)); + ZR_ASSERT(ptr); + s.handle.ptr = ptr; + s.w = 0; s.h = 0; + s.region[0] = 0; + s.region[1] = 0; + s.region[2] = 0; + s.region[3] = 0; + return s; +} + +struct zr_image +zr_image_id(int id) +{ + struct zr_image s; + zr_zero(&s, sizeof(s)); + s.handle.id = id; + s.w = 0; s.h = 0; + s.region[0] = 0; + s.region[1] = 0; + s.region[2] = 0; + s.region[3] = 0; + return s; +} + +int +zr_image_is_subimage(const struct zr_image* img) +{ + ZR_ASSERT(img); + return !(img->w == 0 && img->h == 0); +} + +static void +zr_unify(struct zr_rect *clip, const struct zr_rect *a, float x0, float y0, + float x1, float y1) +{ + ZR_ASSERT(a); + ZR_ASSERT(clip); + clip->x = MAX(a->x, x0); + clip->y = MAX(a->y, y0); + clip->w = MIN(a->x + a->w, x1) - clip->x; + clip->h = MIN(a->y + a->h, y1) - clip->y; + clip->w = MAX(0, clip->w); + clip->h = MAX(0, clip->h); +} + +static void +zr_triangle_from_direction(struct zr_vec2 *result, struct zr_rect r, + float pad_x, float pad_y, enum zr_heading direction) +{ + float w_half, h_half; + ZR_ASSERT(result); + + r.w = MAX(2 * pad_x, r.w); + r.h = MAX(2 * pad_y, r.h); + r.w = r.w - 2 * pad_x; + r.h = r.h - 2 * pad_y; + + r.x = r.x + pad_x; + r.y = r.y + pad_y; + + w_half = r.w / 2.0f; + h_half = r.h / 2.0f; + + if (direction == ZR_UP) { + result[0] = zr_vec2(r.x + w_half, r.y); + result[1] = zr_vec2(r.x + r.w, r.y + r.h); + result[2] = zr_vec2(r.x, r.y + r.h); + } else if (direction == ZR_RIGHT) { + result[0] = zr_vec2(r.x, r.y); + result[1] = zr_vec2(r.x + r.w, r.y + h_half); + result[2] = zr_vec2(r.x, r.y + r.h); + } else if (direction == ZR_DOWN) { + result[0] = zr_vec2(r.x, r.y); + result[1] = zr_vec2(r.x + r.w, r.y); + result[2] = zr_vec2(r.x + w_half, r.y + r.h); + } else { + result[0] = zr_vec2(r.x, r.y + h_half); + result[1] = zr_vec2(r.x + r.w, r.y); + result[2] = zr_vec2(r.x + r.w, r.y + r.h); + } +} + +static zr_size +zr_string_float_limit(char *string, int prec) +{ + int dot = 0; + char *c = string; + while (*c) { + if (*c == '.') { + dot = 1; + c++; + continue; + } + if (dot == (prec+1)) { + *c = 0; + break; + } + if (dot > 0) dot++; + c++; + } + return (zr_size)(c - string); +} +/* ============================================================== + * + * UTF-8 + * + * ===============================================================*/ +static const zr_byte zr_utfbyte[ZR_UTF_SIZE+1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const zr_byte zr_utfmask[ZR_UTF_SIZE+1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const zr_uint zr_utfmin[ZR_UTF_SIZE+1] = {0, 0, 0x80, 0x800, 0x10000}; +static const zr_uint zr_utfmax[ZR_UTF_SIZE+1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static zr_size +zr_utf_validate(zr_rune *u, zr_size i) +{ + ZR_ASSERT(u); + if (!u) return 0; + if (!ZR_BETWEEN(*u, zr_utfmin[i], zr_utfmax[i]) || + ZR_BETWEEN(*u, 0xD800, 0xDFFF)) + *u = ZR_UTF_INVALID; + for (i = 1; *u > zr_utfmax[i]; ++i); + return i; +} + +static zr_rune +zr_utf_decode_byte(char c, zr_size *i) +{ + ZR_ASSERT(i); + if (!i) return 0; + for(*i = 0; *i < ZR_LEN(zr_utfmask); ++(*i)) { + if (((zr_byte)c & zr_utfmask[*i]) == zr_utfbyte[*i]) + return (zr_byte)(c & ~zr_utfmask[*i]); + } + return 0; +} + +zr_size +zr_utf_decode(const char *c, zr_rune *u, zr_size clen) +{ + zr_size i, j, len, type=0; + zr_rune udecoded; + + ZR_ASSERT(c); + ZR_ASSERT(u); + + if (!c || !u) return 0; + if (!clen) return 0; + *u = ZR_UTF_INVALID; + + udecoded = zr_utf_decode_byte(c[0], &len); + if (!ZR_BETWEEN(len, 1, ZR_UTF_SIZE)) + return 1; + + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | zr_utf_decode_byte(c[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *u = udecoded; + zr_utf_validate(u, len); + return len; +} + +static char +zr_utf_encode_byte(zr_rune u, zr_size i) +{ + return (char)((zr_utfbyte[i]) | ((zr_byte)u & ~zr_utfmask[i])); +} + +zr_size +zr_utf_encode(zr_rune u, char *c, zr_size clen) +{ + zr_size len, i; + len = zr_utf_validate(&u, 0); + if (clen < len || !len || len > ZR_UTF_SIZE) + return 0; + + for (i = len - 1; i != 0; --i) { + c[i] = zr_utf_encode_byte(u, 0); + u >>= 6; + } + c[0] = zr_utf_encode_byte(u, len); + return len; +} + +zr_size +zr_utf_len(const char *str, zr_size len) +{ + const char *text; + zr_size glyphs = 0; + zr_size text_len; + zr_size glyph_len; + zr_size src_len = 0; + zr_rune unicode; + + ZR_ASSERT(str); + if (!str || !len) return 0; + + text = str; + text_len = len; + glyph_len = zr_utf_decode(text, &unicode, text_len); + while (glyph_len && src_len < len) { + glyphs++; + src_len = src_len + glyph_len; + glyph_len = zr_utf_decode(text + src_len, &unicode, text_len - src_len); + } + return glyphs; +} + +static const char* +zr_utf_at(const char *buffer, zr_size length, int index, + zr_rune *unicode, zr_size *len) +{ + int i = 0; + zr_size src_len = 0; + zr_size glyph_len = 0; + const char *text; + zr_size text_len; + + ZR_ASSERT(buffer); + ZR_ASSERT(unicode); + ZR_ASSERT(len); + + if (!buffer || !unicode || !len) return 0; + if (index < 0) { + *unicode = ZR_UTF_INVALID; + *len = 0; + return 0; + } + + text = buffer; + text_len = length; + glyph_len = zr_utf_decode(text, unicode, text_len); + while (glyph_len) { + if (i == index) { + *len = glyph_len; + break; + } + + i++; + src_len = src_len + glyph_len; + glyph_len = zr_utf_decode(text + src_len, unicode, text_len - src_len); + } + if (i != index) return 0; + return buffer + src_len; +} + +/* + * ============================================================== + * + * USER FONT + * + * =============================================================== + */ +static zr_size +zr_user_font_glyph_index_at_pos(const struct zr_user_font *font, const char *text, + zr_size text_len, float xoff) +{ + zr_rune unicode; + zr_size glyph_offset = 0; + zr_size glyph_len = zr_utf_decode(text, &unicode, text_len); + zr_size text_width = font->width(font->userdata, font->height, text, glyph_len); + zr_size src_len = glyph_len; + + while (text_len && glyph_len) { + if (text_width >= xoff) + return glyph_offset; + + glyph_offset++; + text_len -= glyph_len; + glyph_len = zr_utf_decode(text + src_len, &unicode, text_len); + src_len += glyph_len; + text_width = font->width(font->userdata, font->height, text, src_len); + } + return glyph_offset; +} + +static zr_size +zr_use_font_glyph_clamp(const struct zr_user_font *font, const char *text, + zr_size text_len, float space, zr_size *glyphs, float *text_width) +{ + zr_size glyph_len = 0; + float last_width = 0; + zr_rune unicode = 0; + float width = 0; + zr_size len = 0; + zr_size g = 0; + + glyph_len = zr_utf_decode(text, &unicode, text_len); + while (glyph_len && (width < space) && (len < text_len)) { + zr_size s; + len += glyph_len; + s = font->width(font->userdata, font->height, text, len); + + last_width = width; + width = (float)s; + glyph_len = zr_utf_decode(&text[len], &unicode, text_len - len); + g++; + } + + *glyphs = g; + *text_width = last_width; + return len; +} + +static zr_size +zr_user_font_glyphs_fitting_in_space(const struct zr_user_font *font, + const char *text, zr_size len, float space, zr_size *row_len, + zr_size *glyphs, float *text_width, int has_newline) +{ + zr_size glyph_len; + zr_size width = 0; + zr_rune unicode = 0; + zr_size text_len = 0; + zr_size row_advance = 0; + + ZR_ASSERT(glyphs); + ZR_ASSERT(text_width); + ZR_ASSERT(row_len); + + *glyphs = 0; + *row_len = 0; + *text_width = 0; + + glyph_len = text_len = zr_utf_decode(text, &unicode, len); + if (!glyph_len) return 0; + width = font->width(font->userdata, font->height, text, text_len); + while ((width <= space) && (text_len <= len) && glyph_len) { + *text_width = width; + *glyphs+=1; + *row_len = text_len; + row_advance += glyph_len; + + if (has_newline && (unicode == '\n' || unicode == '\r')) { + zr_rune next = 0; + zr_utf_decode(text+text_len, &next, len - text_len); + if (unicode == '\r') { + *row_len-=1;; + } else if ((unicode == '\n') && (next == '\r')) { + *row_len-= 2; + } else { + *row_len-=1; + } + *text_width = font->width(font->userdata, font->height, text, *row_len); + break; + } + glyph_len = zr_utf_decode(text + text_len, &unicode, len - text_len); + text_len += glyph_len; + width = font->width(font->userdata, font->height, text, text_len); + } + return row_advance; +} + +/* ============================================================== + * + * BUFFER + * + * ===============================================================*/ +void +zr_buffer_init(struct zr_buffer *b, const struct zr_allocator *a, + zr_size initial_size) +{ + ZR_ASSERT(b); + ZR_ASSERT(a); + ZR_ASSERT(initial_size); + if (!b || !a || !initial_size) return; + + zr_zero(b, sizeof(*b)); + b->type = ZR_BUFFER_DYNAMIC; + b->memory.ptr = a->alloc(a->userdata, initial_size); + b->memory.size = initial_size; + b->size = initial_size; + b->grow_factor = 2.0f; + b->pool = *a; +} + +void +zr_buffer_init_fixed(struct zr_buffer *b, void *m, zr_size size) +{ + ZR_ASSERT(b); + ZR_ASSERT(m); + ZR_ASSERT(size); + if (!b || !m || !size) return; + + zr_zero(b, sizeof(*b)); + b->type = ZR_BUFFER_FIXED; + b->memory.ptr = m; + b->memory.size = size; + b->size = size; +} + +static void* +zr_buffer_align(void *unaligned, zr_size align, zr_size *alignment, + enum zr_buffer_allocation_type type) +{ + void *memory = 0; + if (type == ZR_BUFFER_FRONT) { + if (align) { + memory = ZR_ALIGN_PTR(unaligned, align); + *alignment = (zr_size)((zr_byte*)memory - (zr_byte*)unaligned); + } else { + memory = unaligned; + *alignment = 0; + } + } else { + if (align) { + memory = ZR_ALIGN_PTR_BACK(unaligned, align); + *alignment = (zr_size)((zr_byte*)unaligned - (zr_byte*)memory); + } else { + memory = unaligned; + *alignment = 0; + } + } + return memory; +} + +static void* +zr_buffer_realloc(struct zr_buffer *b, zr_size capacity, zr_size *size) +{ + void *temp; + zr_size buffer_size; + + ZR_ASSERT(b); + ZR_ASSERT(size); + if (!b || !size || !b->pool.alloc || !b->pool.free) + return 0; + + buffer_size = b->memory.size; + temp = b->pool.alloc(b->pool.userdata, capacity); + ZR_ASSERT(temp); + if (!temp) return 0; + zr_memcopy(temp, b->memory.ptr, buffer_size); + b->pool.free(b->pool.userdata, b->memory.ptr); + *size = capacity; + + if (b->size == buffer_size) { + /* no back buffer so just set correct size */ + b->size = capacity; + return temp; + } else { + /* copy back buffer to the end of the new buffer */ + void *dst, *src; + zr_size back_size; + back_size = buffer_size - b->size; + dst = zr_ptr_add(void, temp, capacity - back_size); + src = zr_ptr_add(void, temp, b->size); + zr_memcopy(dst, src, back_size); + b->size = capacity - back_size; + } + return temp; +} + +static void* +zr_buffer_alloc(struct zr_buffer *b, enum zr_buffer_allocation_type type, + zr_size size, zr_size align) +{ + int full; + zr_size cap; + zr_size alignment; + void *unaligned; + void *memory; + + ZR_ASSERT(b); + ZR_ASSERT(size); + if (!b || !size) return 0; + b->needed += size; + + /* calculate total size with needed alignment + size */ + if (type == ZR_BUFFER_FRONT) + unaligned = zr_ptr_add(void, b->memory.ptr, b->allocated); + else unaligned = zr_ptr_add(void, b->memory.ptr, b->size - size); + memory = zr_buffer_align(unaligned, align, &alignment, type); + + /* check if buffer has enough memory*/ + if (type == ZR_BUFFER_FRONT) + full = ((b->allocated + size + alignment) > b->size); + else full = ((b->size - (size + alignment)) <= b->allocated); + + if (full) { + /* buffer is full so allocate bigger buffer if dynamic */ + ZR_ASSERT(b->type == ZR_BUFFER_DYNAMIC); + ZR_ASSERT(b->pool.alloc && b->pool.free); + if (b->type != ZR_BUFFER_DYNAMIC || !b->pool.alloc || !b->pool.free) + return 0; + + cap = (zr_size)((float)b->memory.size * b->grow_factor); + cap = MAX(cap, zr_round_up_pow2((zr_uint)(b->allocated + size))); + b->memory.ptr = zr_buffer_realloc(b, cap, &b->memory.size); + if (!b->memory.ptr) return 0; + + /* align newly allocated pointer */ + if (type == ZR_BUFFER_FRONT) + unaligned = zr_ptr_add(void, b->memory.ptr, b->allocated); + else unaligned = zr_ptr_add(void, b->memory.ptr, b->size); + memory = zr_buffer_align(unaligned, align, &alignment, type); + } + + if (type == ZR_BUFFER_FRONT) + b->allocated += size + alignment; + else b->size -= (size + alignment); + b->needed += alignment; + b->calls++; + return memory; +} + +static void +zr_buffer_mark(struct zr_buffer *buffer, enum zr_buffer_allocation_type type) +{ + ZR_ASSERT(buffer); + if (!buffer) return; + buffer->marker[type].active = zr_true; + if (type == ZR_BUFFER_BACK) + buffer->marker[type].offset = buffer->size; + else buffer->marker[type].offset = buffer->allocated; +} + +static void +zr_buffer_reset(struct zr_buffer *buffer, enum zr_buffer_allocation_type type) +{ + ZR_ASSERT(buffer); + if (!buffer) return; + if (type == ZR_BUFFER_BACK) { + /* reset back buffer either back to marker or empty */ + buffer->needed -= (buffer->memory.size - buffer->marker[type].offset); + if (buffer->marker[type].active) + buffer->size = buffer->marker[type].offset; + else buffer->size = buffer->memory.size; + buffer->marker[type].active = zr_false; + } else { + /* reset front buffer either back to back marker or empty */ + buffer->needed -= (buffer->allocated - buffer->marker[type].offset); + if (buffer->marker[type].active) + buffer->allocated = buffer->marker[type].offset; + else buffer->allocated = 0; + buffer->marker[type].active = zr_false; + } +} + +static void +zr_buffer_clear(struct zr_buffer *b) +{ + ZR_ASSERT(b); + if (!b) return; + b->allocated = 0; + b->size = b->memory.size; + b->calls = 0; + b->needed = 0; +} + +void +zr_buffer_free(struct zr_buffer *b) +{ + ZR_ASSERT(b); + if (!b || !b->memory.ptr) return; + if (b->type == ZR_BUFFER_FIXED) return; + if (!b->pool.free) return; + ZR_ASSERT(b->pool.free); + b->pool.free(b->pool.userdata, b->memory.ptr); +} + +void +zr_buffer_info(struct zr_memory_status *s, struct zr_buffer *b) +{ + ZR_ASSERT(b); + ZR_ASSERT(s); + if (!s || !b) return; + s->allocated = b->allocated; + s->size = b->memory.size; + s->needed = b->needed; + s->memory = b->memory.ptr; + s->calls = b->calls; +} + +void* +zr_buffer_memory(struct zr_buffer *buffer) +{ + ZR_ASSERT(buffer); + if (!buffer) return 0; + return buffer->memory.ptr; +} + +zr_size +zr_buffer_total(struct zr_buffer *buffer) +{ + ZR_ASSERT(buffer); + if (!buffer) return 0; + return buffer->memory.size; +} + +/* + * ============================================================== + * + * Command buffer + * + * =============================================================== +*/ +static void +zr_command_buffer_init(struct zr_command_buffer *cmdbuf, + struct zr_buffer *buffer, enum zr_command_clipping clip) +{ + ZR_ASSERT(cmdbuf); + ZR_ASSERT(buffer); + if (!cmdbuf || !buffer) return; + cmdbuf->base = buffer; + cmdbuf->use_clipping = clip; + cmdbuf->begin = buffer->allocated; + cmdbuf->end = buffer->allocated; + cmdbuf->last = buffer->allocated; +} + +static void +zr_command_buffer_reset(struct zr_command_buffer *buffer) +{ + ZR_ASSERT(buffer); + if (!buffer) return; + buffer->begin = 0; + buffer->end = 0; + buffer->last = 0; + buffer->clip = zr_null_rect; +#if ZR_COMPILE_WITH_COMMAND_USERDATA + buffer->userdata.ptr = 0; +#endif +} + +static void* +zr_command_buffer_push(struct zr_command_buffer* b, + enum zr_command_type t, zr_size size) +{ + static const zr_size align = ZR_ALIGNOF(struct zr_command); + struct zr_command *cmd; + zr_size alignment; + void *unaligned; + void *memory; + + ZR_ASSERT(b); + ZR_ASSERT(b->base); + if (!b) return 0; + + cmd = (struct zr_command*)zr_buffer_alloc(b->base,ZR_BUFFER_FRONT,size,align); + if (!cmd) return 0; + + /* make sure the offset to the next command is aligned */ + b->last = (zr_size)((zr_byte*)cmd - (zr_byte*)b->base->memory.ptr); + unaligned = (zr_byte*)cmd + size; + memory = ZR_ALIGN_PTR(unaligned, align); + alignment = (zr_size)((zr_byte*)memory - (zr_byte*)unaligned); + + cmd->type = t; + cmd->next = b->base->allocated + alignment; +#if ZR_COMPILE_WITH_COMMAND_USERDATA + cmd->userdata = b->userdata; +#endif + b->end = cmd->next; + return cmd; +} + +void +zr_draw_scissor(struct zr_command_buffer *b, struct zr_rect r) +{ + struct zr_command_scissor *cmd; + ZR_ASSERT(b); + if (!b) return; + + b->clip.x = r.x; + b->clip.y = r.y; + b->clip.w = r.w; + b->clip.h = r.h; + cmd = (struct zr_command_scissor*) + zr_command_buffer_push(b, ZR_COMMAND_SCISSOR, sizeof(*cmd)); + + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)MAX(0, r.w); + cmd->h = (unsigned short)MAX(0, r.h); +} + +void +zr_draw_line(struct zr_command_buffer *b, float x0, float y0, + float x1, float y1, struct zr_color c) +{ + struct zr_command_line *cmd; + ZR_ASSERT(b); + if (!b) return; + cmd = (struct zr_command_line*) + zr_command_buffer_push(b, ZR_COMMAND_LINE, sizeof(*cmd)); + if (!cmd) return; + cmd->begin.x = (short)x0; + cmd->begin.y = (short)y0; + cmd->end.x = (short)x1; + cmd->end.y = (short)y1; + cmd->color = c; +} + +void +zr_draw_curve(struct zr_command_buffer *b, float ax, float ay, + float ctrl0x, float ctrl0y, float ctrl1x, float ctrl1y, + float bx, float by, struct zr_color col) +{ + struct zr_command_curve *cmd; + ZR_ASSERT(b); + if (!b) return; + + cmd = (struct zr_command_curve*) + zr_command_buffer_push(b, ZR_COMMAND_CURVE, sizeof(*cmd)); + if (!cmd) return; + cmd->begin.x = (short)ax; + cmd->begin.y = (short)ay; + cmd->ctrl[0].x = (short)ctrl0x; + cmd->ctrl[0].y = (short)ctrl0y; + cmd->ctrl[1].x = (short)ctrl1x; + cmd->ctrl[1].y = (short)ctrl1y; + cmd->end.x = (short)bx; + cmd->end.y = (short)by; + cmd->color = col; +} + +void +zr_draw_rect(struct zr_command_buffer *b, struct zr_rect rect, + float rounding, struct zr_color c) +{ + struct zr_command_rect *cmd; + ZR_ASSERT(b); + if (!b) return; + if (b->use_clipping) { + const struct zr_rect *clip = &b->clip; + if (!ZR_INTERSECT(rect.x, rect.y, rect.w, rect.h, + clip->x, clip->y, clip->w, clip->h)) return; + } + + cmd = (struct zr_command_rect*) + zr_command_buffer_push(b, ZR_COMMAND_RECT, sizeof(*cmd)); + if (!cmd) return; + cmd->rounding = (unsigned int)rounding; + cmd->x = (short)rect.x; + cmd->y = (short)rect.y; + cmd->w = (unsigned short)MAX(0, rect.w); + cmd->h = (unsigned short)MAX(0, rect.h); + cmd->color = c; +} + +void +zr_draw_circle(struct zr_command_buffer *b, struct zr_rect r, + struct zr_color c) +{ + struct zr_command_circle *cmd; + ZR_ASSERT(b); + if (!b) return; + if (b->use_clipping) { + const struct zr_rect *clip = &b->clip; + if (!ZR_INTERSECT(r.x, r.y, r.w, r.h, clip->x, clip->y, clip->w, clip->h)) + return; + } + + cmd = (struct zr_command_circle*) + zr_command_buffer_push(b, ZR_COMMAND_CIRCLE, sizeof(*cmd)); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)MAX(r.w, 0); + cmd->h = (unsigned short)MAX(r.h, 0); + cmd->color = c; +} + +void +zr_draw_arc(struct zr_command_buffer *b, float cx, + float cy, float radius, float a_min, float a_max, struct zr_color c) +{ + struct zr_command_arc *cmd; + cmd = (struct zr_command_arc*) + zr_command_buffer_push(b, ZR_COMMAND_ARC, sizeof(*cmd)); + if (!cmd) return; + cmd->cx = (short)cx; + cmd->cy = (short)cy; + cmd->r = (unsigned short)radius; + cmd->a[0] = a_min; + cmd->a[1] = a_max; + cmd->color = c; +} + +void +zr_draw_triangle(struct zr_command_buffer *b,float x0,float y0, + float x1, float y1, float x2, float y2, struct zr_color c) +{ + struct zr_command_triangle *cmd; + ZR_ASSERT(b); + if (!b) return; + if (b->use_clipping) { + const struct zr_rect *clip = &b->clip; + if (!ZR_INBOX(x0, y0, clip->x, clip->y, clip->w, clip->h) || + !ZR_INBOX(x1, y1, clip->x, clip->y, clip->w, clip->h) || + !ZR_INBOX(x2, y2, clip->x, clip->y, clip->w, clip->h)) + return; + } + + cmd = (struct zr_command_triangle*) + zr_command_buffer_push(b, ZR_COMMAND_TRIANGLE, sizeof(*cmd)); + if (!cmd) return; + cmd->a.x = (short)x0; + cmd->a.y = (short)y0; + cmd->b.x = (short)x1; + cmd->b.y = (short)y1; + cmd->c.x = (short)x2; + cmd->c.y = (short)y2; + cmd->color = c; +} + +void +zr_draw_image(struct zr_command_buffer *b, struct zr_rect r, + struct zr_image *img) +{ + struct zr_command_image *cmd; + ZR_ASSERT(b); + if (!b) return; + if (b->use_clipping) { + const struct zr_rect *c = &b->clip; + if (!ZR_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h)) + return; + } + + cmd = (struct zr_command_image*) + zr_command_buffer_push(b, ZR_COMMAND_IMAGE, sizeof(*cmd)); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)MAX(0, r.w); + cmd->h = (unsigned short)MAX(0, r.h); + cmd->img = *img; +} + +void +zr_draw_text(struct zr_command_buffer *b, struct zr_rect r, + const char *string, zr_size length, const struct zr_user_font *font, + struct zr_color bg, struct zr_color fg) +{ + zr_size text_width = 0; + struct zr_command_text *cmd; + ZR_ASSERT(b); + ZR_ASSERT(font); + if (!b || !string || !length) return; + if (b->use_clipping) { + const struct zr_rect *c = &b->clip; + if (!ZR_INTERSECT(r.x, r.y, r.w, r.h, c->x, c->y, c->w, c->h)) + return; + } + + /* make sure text fits inside bounds */ + text_width = font->width(font->userdata, font->height, string, length); + if (text_width > r.w){ + float txt_width = (float)text_width; + zr_size glyphs = 0; + length = zr_use_font_glyph_clamp(font, string, length, + r.w, &glyphs, &txt_width); + } + + if (!length) return; + cmd = (struct zr_command_text*) + zr_command_buffer_push(b, ZR_COMMAND_TEXT, sizeof(*cmd) + length + 1); + if (!cmd) return; + cmd->x = (short)r.x; + cmd->y = (short)r.y; + cmd->w = (unsigned short)r.w; + cmd->h = (unsigned short)r.h; + cmd->background = bg; + cmd->foreground = fg; + cmd->font = font; + cmd->length = length; + cmd->height = font->height; + zr_memcopy(cmd->string, string, length); + cmd->string[length] = '\0'; +} + +/* ============================================================== + * + * CANVAS + * + * ===============================================================*/ +#if ZR_COMPILE_WITH_VERTEX_BUFFER +static void +zr_canvas_init(struct zr_canvas *list) +{ + zr_size i = 0; + zr_zero(list, sizeof(*list)); + for (i = 0; i < ZR_LEN(list->circle_vtx); ++i) { + const float a = ((float)i / (float)ZR_LEN(list->circle_vtx)) * 2 * ZR_PI; + list->circle_vtx[i].x = (float)zr_cos(a); + list->circle_vtx[i].y = (float)zr_sin(a); + } +} + +static void +zr_canvas_setup(struct zr_canvas *list, float global_alpha, struct zr_buffer *cmds, + struct zr_buffer *vertices, struct zr_buffer *elements, + struct zr_draw_null_texture null, + enum zr_anti_aliasing line_AA, enum zr_anti_aliasing shape_AA) +{ + list->null = null; + list->clip_rect = zr_null_rect; + list->vertices = vertices; + list->elements = elements; + list->buffer = cmds; + list->line_AA = line_AA; + list->shape_AA = shape_AA; + list->global_alpha = global_alpha; +} + +static void +zr_canvas_clear(struct zr_canvas *list) +{ + ZR_ASSERT(list); + if (!list) return; + if (list->buffer) + zr_buffer_clear(list->buffer); + if (list->elements) + zr_buffer_clear(list->vertices); + if (list->vertices) + zr_buffer_clear(list->elements); + + list->element_count = 0; + list->vertex_count = 0; + list->cmd_offset = 0; + list->cmd_count = 0; + list->path_count = 0; + list->vertices = 0; + list->elements = 0; + list->clip_rect = zr_null_rect; +} + +static struct zr_vec2* +zr_canvas_alloc_path(struct zr_canvas *list, zr_size count) +{ + struct zr_vec2 *points; + static const zr_size point_align = ZR_ALIGNOF(struct zr_vec2); + static const zr_size point_size = sizeof(struct zr_vec2); + points = (struct zr_vec2*) + zr_buffer_alloc(list->buffer, ZR_BUFFER_FRONT, + point_size * count, point_align); + + if (!points) return 0; + if (!list->path_offset) { + void *memory = zr_buffer_memory(list->buffer); + list->path_offset = (unsigned int)((zr_byte*)points - (zr_byte*)memory); + } + list->path_count += (unsigned int)count; + return points; +} + +static struct zr_vec2 +zr_canvas_path_last(struct zr_canvas *list) +{ + void *memory; + struct zr_vec2 *point; + ZR_ASSERT(list->path_count); + memory = zr_buffer_memory(list->buffer); + point = zr_ptr_add(struct zr_vec2, memory, list->path_offset); + point += (list->path_count-1); + return *point; +} + +static struct zr_draw_command* +zr_canvas_push_command(struct zr_canvas *list, struct zr_rect clip, + zr_handle texture) +{ + static const zr_size cmd_align = ZR_ALIGNOF(struct zr_draw_command); + static const zr_size cmd_size = sizeof(struct zr_draw_command); + struct zr_draw_command *cmd; + + ZR_ASSERT(list); + cmd = (struct zr_draw_command*) + zr_buffer_alloc(list->buffer, ZR_BUFFER_BACK, cmd_size, cmd_align); + + if (!cmd) return 0; + if (!list->cmd_count) { + zr_byte *memory = (zr_byte*)zr_buffer_memory(list->buffer); + zr_size total = zr_buffer_total(list->buffer); + memory = zr_ptr_add(zr_byte, memory, total); + list->cmd_offset = (zr_size)(memory - (zr_byte*)cmd); + } + + cmd->elem_count = 0; + cmd->clip_rect = clip; + cmd->texture = texture; + + list->cmd_count++; + list->clip_rect = clip; + return cmd; +} + +static struct zr_draw_command* +zr_canvas_command_last(struct zr_canvas *list) +{ + void *memory; + zr_size size; + struct zr_draw_command *cmd; + ZR_ASSERT(list->cmd_count); + + memory = zr_buffer_memory(list->buffer); + size = zr_buffer_total(list->buffer); + cmd = zr_ptr_add(struct zr_draw_command, memory, size - list->cmd_offset); + return (cmd - (list->cmd_count-1)); +} + +static void +zr_canvas_add_clip(struct zr_canvas *list, struct zr_rect rect) +{ + ZR_ASSERT(list); + if (!list) return; + if (!list->cmd_count) { + zr_canvas_push_command(list, rect, list->null.texture); + } else { + struct zr_draw_command *prev = zr_canvas_command_last(list); + zr_canvas_push_command(list, rect, prev->texture); + } +} + +static void +zr_canvas_push_image(struct zr_canvas *list, zr_handle texture) +{ + ZR_ASSERT(list); + if (!list) return; + if (!list->cmd_count) { + zr_canvas_push_command(list, zr_null_rect, list->null.texture); + } else { + struct zr_draw_command *prev = zr_canvas_command_last(list); + if (prev->texture.id != texture.id) + zr_canvas_push_command(list, prev->clip_rect, texture); + } +} + +#if ZR_COMPILE_WITH_COMMAND_USERDATA +static void +zr_canvas_push_userdata(struct zr_canvas *list, zr_handle userdata) +{ + ZR_ASSERT(list); + if (!list) return; + if (!list->cmd_count) { + struct zr_draw_command *prev; + zr_canvas_push_command(list, zr_null_rect, list->null.texture); + prev = zr_canvas_command_last(list); + prev->userdata = userdata; + } else { + struct zr_draw_command *prev = zr_canvas_command_last(list); + if (prev->userdata.ptr != userdata.ptr) { + zr_canvas_push_command(list, prev->clip_rect, prev->texture); + prev = zr_canvas_command_last(list); + prev->userdata = userdata; + } + } +} +#endif + +static struct zr_draw_vertex* +zr_canvas_alloc_vertices(struct zr_canvas *list, zr_size count) +{ + struct zr_draw_vertex *vtx; + static const zr_size vtx_align = ZR_ALIGNOF(struct zr_draw_vertex); + static const zr_size vtx_size = sizeof(struct zr_draw_vertex); + ZR_ASSERT(list); + if (!list) return 0; + + vtx = (struct zr_draw_vertex*) + zr_buffer_alloc(list->vertices, ZR_BUFFER_FRONT, vtx_size*count, vtx_align); + if (!vtx) return 0; + list->vertex_count += (unsigned int)count; + return vtx; +} + +static zr_draw_index* +zr_canvas_alloc_elements(struct zr_canvas *list, zr_size count) +{ + zr_draw_index *ids; + struct zr_draw_command *cmd; + static const zr_size elem_align = ZR_ALIGNOF(zr_draw_index); + static const zr_size elem_size = sizeof(zr_draw_index); + ZR_ASSERT(list); + if (!list) return 0; + + ids = (zr_draw_index*) + zr_buffer_alloc(list->elements, ZR_BUFFER_FRONT, elem_size*count, elem_align); + if (!ids) return 0; + cmd = zr_canvas_command_last(list); + list->element_count += (unsigned int)count; + cmd->elem_count += (unsigned int)count; + return ids; +} + +static struct zr_draw_vertex +zr_draw_vertex(struct zr_vec2 pos, struct zr_vec2 uv, zr_draw_vertex_color col) +{ + struct zr_draw_vertex out; + out.position = pos; + out.uv = uv; + out.col = col; + return out; +} + +static void +zr_canvas_add_poly_line(struct zr_canvas *list, struct zr_vec2 *points, + const unsigned int points_count, struct zr_color color, int closed, + float thickness, enum zr_anti_aliasing aliasing) +{ + zr_size count; + int thick_line; + zr_draw_vertex_color col; + ZR_ASSERT(list); + if (!list || points_count < 2) return; + + color.a *= list->global_alpha; + col = zr_color32(color); + count = points_count; + if (!closed) count = points_count-1; + thick_line = thickness > 1.0f; + +#if ZR_COMPILE_WITH_COMMAND_USERDATA + zr_canvas_push_userdata(list, list->userdata); +#endif + + if (aliasing == ZR_ANTI_ALIASING_ON) { + /* ANTI-ALIASED STROKE */ + const float AA_SIZE = 1.0f; + static const zr_size pnt_align = ZR_ALIGNOF(struct zr_vec2); + static const zr_size pnt_size = sizeof(struct zr_vec2); + const zr_draw_vertex_color col_trans = col & 0x00ffffff; + + /* allocate vertices and elements */ + zr_size i1 = 0; + zr_size index = list->vertex_count; + const zr_size idx_count = (thick_line) ? (count * 18) : (count * 12); + const zr_size vtx_count = (thick_line) ? (points_count * 4): (points_count *3); + struct zr_draw_vertex *vtx = zr_canvas_alloc_vertices(list, vtx_count); + zr_draw_index *ids = zr_canvas_alloc_elements(list, idx_count); + + zr_size size; + struct zr_vec2 *normals, *temp; + if (!vtx || !ids) return; + + /* temporary allocate normals + points */ + zr_buffer_mark(list->vertices, ZR_BUFFER_FRONT); + size = pnt_size * ((thick_line) ? 5 : 3) * points_count; + normals = (struct zr_vec2*) + zr_buffer_alloc(list->vertices, ZR_BUFFER_FRONT, size, pnt_align); + if (!normals) return; + temp = normals + points_count; + + /* calculate normals */ + for (i1 = 0; i1 < count; ++i1) { + const zr_size i2 = ((i1 + 1) == points_count) ? 0 : (i1 + 1); + struct zr_vec2 diff = zr_vec2_sub(points[i2], points[i1]); + float len; + + /* vec2 inverted lenth */ + len = zr_vec2_len_sqr(diff); + if (len != 0.0f) + len = zr_inv_sqrt(len); + else len = 1.0f; + + diff = zr_vec2_muls(diff, len); + normals[i1].x = diff.y; + normals[i1].y = -diff.x; + } + + if (!closed) + normals[points_count-1] = normals[points_count-2]; + + if (!thick_line) { + zr_size idx1, i; + if (!closed) { + struct zr_vec2 d; + temp[0] = zr_vec2_add(points[0], zr_vec2_muls(normals[0], AA_SIZE)); + temp[1] = zr_vec2_sub(points[0], zr_vec2_muls(normals[0], AA_SIZE)); + d = zr_vec2_muls(normals[points_count-1], AA_SIZE); + temp[(points_count-1) * 2 + 0] = zr_vec2_add(points[points_count-1], d); + temp[(points_count-1) * 2 + 1] = zr_vec2_sub(points[points_count-1], d); + } + + /* fill elements */ + idx1 = index; + for (i1 = 0; i1 < count; i1++) { + struct zr_vec2 dm; + float dmr2; + zr_size i2 = ((i1 + 1) == points_count) ? 0 : (i1 + 1); + zr_size idx2 = ((i1+1) == points_count) ? index: (idx1 + 3); + + /* average normals */ + dm = zr_vec2_muls(zr_vec2_add(normals[i1], normals[i2]), 0.5f); + dmr2 = dm.x * dm.x + dm.y* dm.y; + if (dmr2 > 0.000001f) { + float scale = 1.0f/dmr2; + scale = MIN(100.0f, scale); + dm = zr_vec2_muls(dm, scale); + } + + dm = zr_vec2_muls(dm, AA_SIZE); + temp[i2*2+0] = zr_vec2_add(points[i2], dm); + temp[i2*2+1] = zr_vec2_sub(points[i2], dm); + + ids[0] = (zr_draw_index)(idx2 + 0); ids[1] = (zr_draw_index)(idx1+0); + ids[2] = (zr_draw_index)(idx1 + 2); ids[3] = (zr_draw_index)(idx1+2); + ids[4] = (zr_draw_index)(idx2 + 2); ids[5] = (zr_draw_index)(idx2+0); + ids[6] = (zr_draw_index)(idx2 + 1); ids[7] = (zr_draw_index)(idx1+1); + ids[8] = (zr_draw_index)(idx1 + 0); ids[9] = (zr_draw_index)(idx1+0); + ids[10]= (zr_draw_index)(idx2 + 0); ids[11]= (zr_draw_index)(idx2+1); + ids += 12; + idx1 = idx2; + } + + /* fill vertices */ + for (i = 0; i < points_count; ++i) { + const struct zr_vec2 uv = list->null.uv; + vtx[0] = zr_draw_vertex(points[i], uv, col); + vtx[1] = zr_draw_vertex(temp[i*2+0], uv, col_trans); + vtx[2] = zr_draw_vertex(temp[i*2+1], uv, col_trans); + vtx += 3; + } + } else { + zr_size idx1, i; + const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; + if (!closed) { + struct zr_vec2 d1 = zr_vec2_muls(normals[0], half_inner_thickness + AA_SIZE); + struct zr_vec2 d2 = zr_vec2_muls(normals[0], half_inner_thickness); + + temp[0] = zr_vec2_add(points[0], d1); + temp[1] = zr_vec2_add(points[0], d2); + temp[2] = zr_vec2_sub(points[0], d2); + temp[3] = zr_vec2_sub(points[0], d1); + + d1 = zr_vec2_muls(normals[points_count-1], half_inner_thickness + AA_SIZE); + d2 = zr_vec2_muls(normals[points_count-1], half_inner_thickness); + + temp[(points_count-1)*4+0] = zr_vec2_add(points[points_count-1], d1); + temp[(points_count-1)*4+1] = zr_vec2_add(points[points_count-1], d2); + temp[(points_count-1)*4+2] = zr_vec2_sub(points[points_count-1], d2); + temp[(points_count-1)*4+3] = zr_vec2_sub(points[points_count-1], d1); + } + + /* add all elements */ + idx1 = index; + for (i1 = 0; i1 < count; ++i1) { + struct zr_vec2 dm_out, dm_in; + const zr_size i2 = ((i1+1) == points_count) ? 0: (i1 + 1); + zr_size idx2 = ((i1+1) == points_count) ? index: (idx1 + 4); + + /* average normals */ + struct zr_vec2 dm = zr_vec2_muls(zr_vec2_add(normals[i1], normals[i2]), 0.5f); + float dmr2 = dm.x * dm.x + dm.y* dm.y; + if (dmr2 > 0.000001f) { + float scale = 1.0f/dmr2; + scale = MIN(100.0f, scale); + dm = zr_vec2_muls(dm, scale); + } + + dm_out = zr_vec2_muls(dm, ((half_inner_thickness) + AA_SIZE)); + dm_in = zr_vec2_muls(dm, half_inner_thickness); + temp[i2*4+0] = zr_vec2_add(points[i2], dm_out); + temp[i2*4+1] = zr_vec2_add(points[i2], dm_in); + temp[i2*4+2] = zr_vec2_sub(points[i2], dm_in); + temp[i2*4+3] = zr_vec2_sub(points[i2], dm_out); + + /* add indexes */ + ids[0] = (zr_draw_index)(idx2 + 1); ids[1] = (zr_draw_index)(idx1+1); + ids[2] = (zr_draw_index)(idx1 + 2); ids[3] = (zr_draw_index)(idx1+2); + ids[4] = (zr_draw_index)(idx2 + 2); ids[5] = (zr_draw_index)(idx2+1); + ids[6] = (zr_draw_index)(idx2 + 1); ids[7] = (zr_draw_index)(idx1+1); + ids[8] = (zr_draw_index)(idx1 + 0); ids[9] = (zr_draw_index)(idx1+0); + ids[10]= (zr_draw_index)(idx2 + 0); ids[11] = (zr_draw_index)(idx2+1); + ids[12]= (zr_draw_index)(idx2 + 2); ids[13] = (zr_draw_index)(idx1+2); + ids[14]= (zr_draw_index)(idx1 + 3); ids[15] = (zr_draw_index)(idx1+3); + ids[16]= (zr_draw_index)(idx2 + 3); ids[17] = (zr_draw_index)(idx2+2); + ids += 18; + idx1 = idx2; + } + + /* add vertices */ + for (i = 0; i < points_count; ++i) { + const struct zr_vec2 uv = list->null.uv; + vtx[0] = zr_draw_vertex(temp[i*4+0], uv, col_trans); + vtx[1] = zr_draw_vertex(temp[i*4+1], uv, col); + vtx[2] = zr_draw_vertex(temp[i*4+2], uv, col); + vtx[3] = zr_draw_vertex(temp[i*4+3], uv, col_trans); + vtx += 4; + } + } + + /* free temporary normals + points */ + zr_buffer_reset(list->vertices, ZR_BUFFER_FRONT); + } else { + /* NON ANTI-ALIASED STROKE */ + zr_size i1 = 0; + zr_size idx = list->vertex_count; + const zr_size idx_count = count * 6; + const zr_size vtx_count = count * 4; + struct zr_draw_vertex *vtx = zr_canvas_alloc_vertices(list, vtx_count); + zr_draw_index *ids = zr_canvas_alloc_elements(list, idx_count); + if (!vtx || !ids) return; + + for (i1 = 0; i1 < count; ++i1) { + float dx, dy; + const struct zr_vec2 uv = list->null.uv; + const zr_size i2 = ((i1+1) == points_count) ? 0 : i1 + 1; + const struct zr_vec2 p1 = points[i1]; + const struct zr_vec2 p2 = points[i2]; + struct zr_vec2 diff = zr_vec2_sub(p2, p1); + float len; + + /* vec2 inverted lenth */ + len = zr_vec2_len_sqr(diff); + if (len != 0.0f) + len = zr_inv_sqrt(len); + else len = 1.0f; + diff = zr_vec2_muls(diff, len); + + /* add vertices */ + dx = diff.x * (thickness * 0.5f); + dy = diff.y * (thickness * 0.5f); + + vtx[0] = zr_draw_vertex(zr_vec2(p1.x + dy, p1.y - dx), uv, col); + vtx[1] = zr_draw_vertex(zr_vec2(p2.x + dy, p2.y - dx), uv, col); + vtx[2] = zr_draw_vertex(zr_vec2(p2.x - dy, p2.y + dx), uv, col); + vtx[3] = zr_draw_vertex(zr_vec2(p1.x - dy, p1.y + dx), uv, col); + vtx += 4; + + ids[0] = (zr_draw_index)(idx+0); ids[1] = (zr_draw_index)(idx+1); + ids[2] = (zr_draw_index)(idx+2); ids[3] = (zr_draw_index)(idx+0); + ids[4] = (zr_draw_index)(idx+2); ids[5] = (zr_draw_index)(idx+3); + ids += 6; + idx += 4; + } + } +} + +static void +zr_canvas_add_poly_convex(struct zr_canvas *list, struct zr_vec2 *points, + const unsigned int points_count, struct zr_color color, + enum zr_anti_aliasing aliasing) +{ + static const zr_size pnt_align = ZR_ALIGNOF(struct zr_vec2); + static const zr_size pnt_size = sizeof(struct zr_vec2); + zr_draw_vertex_color col; + ZR_ASSERT(list); + if (!list || points_count < 3) return; + +#if ZR_COMPILE_WITH_COMMAND_USERDATA + zr_canvas_push_userdata(list, list->userdata); +#endif + + color.a *= list->global_alpha; + col = zr_color32(color); + if (aliasing == ZR_ANTI_ALIASING_ON) { + zr_size i = 0; + zr_size i0 = 0, i1 = 0; + + const float AA_SIZE = 1.0f; + const zr_draw_vertex_color col_trans = col & 0x00ffffff; + zr_size index = list->vertex_count; + const zr_size idx_count = (points_count-2)*3 + points_count*6; + const zr_size vtx_count = (points_count*2); + struct zr_draw_vertex *vtx = zr_canvas_alloc_vertices(list, vtx_count); + zr_draw_index *ids = zr_canvas_alloc_elements(list, idx_count); + + unsigned int vtx_inner_idx = (unsigned int)(index + 0); + unsigned int vtx_outer_idx = (unsigned int)(index + 1); + struct zr_vec2 *normals = 0; + zr_size size = 0; + if (!vtx || !ids) return; + + /* temporary allocate normals */ + zr_buffer_mark(list->vertices, ZR_BUFFER_FRONT); + size = pnt_size * points_count; + normals = (struct zr_vec2*) + zr_buffer_alloc(list->vertices, ZR_BUFFER_FRONT, size, pnt_align); + if (!normals) return; + + /* add elements */ + for (i = 2; i < points_count; i++) { + ids[0] = (zr_draw_index)(vtx_inner_idx); + ids[1] = (zr_draw_index)(vtx_inner_idx + ((i-1) << 1)); + ids[2] = (zr_draw_index)(vtx_inner_idx + (i << 1)); + ids += 3; + } + + /* compute normals */ + for (i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) { + struct zr_vec2 p0 = points[i0]; + struct zr_vec2 p1 = points[i1]; + struct zr_vec2 diff = zr_vec2_sub(p1, p0); + + /* vec2 inverted lenth */ + float len = zr_vec2_len_sqr(diff); + if (len != 0.0f) + len = zr_inv_sqrt(len); + else len = 1.0f; + diff = zr_vec2_muls(diff, len); + + normals[i0].x = diff.y; + normals[i0].y = -diff.x; + } + + /* add vertices + indexes */ + for (i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) { + const struct zr_vec2 uv = list->null.uv; + struct zr_vec2 n0 = normals[i0]; + struct zr_vec2 n1 = normals[i1]; + struct zr_vec2 dm = zr_vec2_muls(zr_vec2_add(n0, n1), 0.5f); + + float dmr2 = dm.x*dm.x + dm.y*dm.y; + if (dmr2 > 0.000001f) { + float scale = 1.0f / dmr2; + scale = MIN(scale, 100.0f); + dm = zr_vec2_muls(dm, scale); + } + dm = zr_vec2_muls(dm, AA_SIZE * 0.5f); + + /* add vertices */ + vtx[0] = zr_draw_vertex(zr_vec2_sub(points[i1], dm), uv, col); + vtx[1] = zr_draw_vertex(zr_vec2_add(points[i1], dm), uv, col_trans); + vtx += 2; + + /* add indexes */ + ids[0] = (zr_draw_index)(vtx_inner_idx+(i1<<1)); + ids[1] = (zr_draw_index)(vtx_inner_idx+(i0<<1)); + ids[2] = (zr_draw_index)(vtx_outer_idx+(i0<<1)); + ids[3] = (zr_draw_index)(vtx_outer_idx+(i0<<1)); + ids[4] = (zr_draw_index)(vtx_outer_idx+(i1<<1)); + ids[5] = (zr_draw_index)(vtx_inner_idx+(i1<<1)); + ids += 6; + } + /* free temporary normals + points */ + zr_buffer_reset(list->vertices, ZR_BUFFER_FRONT); + } else { + zr_size i = 0; + zr_size index = list->vertex_count; + const zr_size idx_count = (points_count-2)*3; + const zr_size vtx_count = points_count; + struct zr_draw_vertex *vtx = zr_canvas_alloc_vertices(list, vtx_count); + zr_draw_index *ids = zr_canvas_alloc_elements(list, idx_count); + if (!vtx || !ids) return; + for (i = 0; i < vtx_count; ++i) { + vtx[0] = zr_draw_vertex(points[i], list->null.uv, col); + vtx++; + } + for (i = 2; i < points_count; ++i) { + ids[0] = (zr_draw_index)index; + ids[1] = (zr_draw_index)(index+ i - 1); + ids[2] = (zr_draw_index)(index+i); + ids += 3; + } + } +} + +static void +zr_canvas_path_clear(struct zr_canvas *list) +{ + ZR_ASSERT(list); + if (!list) return; + zr_buffer_reset(list->buffer, ZR_BUFFER_FRONT); + list->path_count = 0; + list->path_offset = 0; +} + +static void +zr_canvas_path_line_to(struct zr_canvas *list, struct zr_vec2 pos) +{ + struct zr_vec2 *points = 0; + struct zr_draw_command *cmd = 0; + ZR_ASSERT(list); + if (!list) return; + if (!list->cmd_count) + zr_canvas_add_clip(list, zr_null_rect); + + cmd = zr_canvas_command_last(list); + if (cmd && cmd->texture.ptr != list->null.texture.ptr) + zr_canvas_push_image(list, list->null.texture); + + points = zr_canvas_alloc_path(list, 1); + if (!points) return; + points[0] = pos; +} + +static void +zr_canvas_path_arc_to_fast(struct zr_canvas *list, struct zr_vec2 center, + float radius, int a_min, int a_max) +{ + ZR_ASSERT(list); + if (!list) return; + if (a_min <= a_max) { + int a = 0; + for (a = a_min; a <= a_max; a++) { + const struct zr_vec2 c = list->circle_vtx[(zr_size)a % ZR_LEN(list->circle_vtx)]; + const float x = center.x + c.x * radius; + const float y = center.y + c.y * radius; + zr_canvas_path_line_to(list, zr_vec2(x, y)); + } + } +} + +static void +zr_canvas_path_arc_to(struct zr_canvas *list, struct zr_vec2 center, + float radius, float a_min, float a_max, unsigned int segments) +{ + unsigned int i = 0; + ZR_ASSERT(list); + if (!list) return; + if (radius == 0.0f) return; + for (i = 0; i <= segments; ++i) { + const float a = a_min + ((float)i / ((float)segments) * (a_max - a_min)); + const float x = center.x + (float)zr_cos(a) * radius; + const float y = center.y + (float)zr_sin(a) * radius; + zr_canvas_path_line_to(list, zr_vec2(x, y)); + } +} + +static void +zr_canvas_path_rect_to(struct zr_canvas *list, struct zr_vec2 a, + struct zr_vec2 b, float rounding) +{ + float r; + ZR_ASSERT(list); + if (!list) return; + r = rounding; + r = MIN(r, ((b.x-a.x) < 0) ? -(b.x-a.x): (b.x-a.x)); + r = MIN(r, ((b.y-a.y) < 0) ? -(b.y-a.y): (b.y-a.y)); + + if (r == 0.0f) { + zr_canvas_path_line_to(list, a); + zr_canvas_path_line_to(list, zr_vec2(b.x,a.y)); + zr_canvas_path_line_to(list, b); + zr_canvas_path_line_to(list, zr_vec2(a.x,b.y)); + } else { + zr_canvas_path_arc_to_fast(list, zr_vec2(a.x + r, a.y + r), r, 6, 9); + zr_canvas_path_arc_to_fast(list, zr_vec2(b.x - r, a.y + r), r, 9, 12); + zr_canvas_path_arc_to_fast(list, zr_vec2(b.x - r, b.y - r), r, 0, 3); + zr_canvas_path_arc_to_fast(list, zr_vec2(a.x + r, b.y - r), r, 3, 6); + } +} + +static void +zr_canvas_path_curve_to(struct zr_canvas *list, struct zr_vec2 p2, + struct zr_vec2 p3, struct zr_vec2 p4, unsigned int num_segments) +{ + unsigned int i_step; + float t_step; + struct zr_vec2 p1; + + ZR_ASSERT(list); + ZR_ASSERT(list->path_count); + if (!list || !list->path_count) return; + num_segments = MAX(num_segments, 1); + + p1 = zr_canvas_path_last(list); + t_step = 1.0f/(float)num_segments; + for (i_step = 1; i_step <= num_segments; ++i_step) { + float t = t_step * (float)i_step; + float u = 1.0f - t; + float w1 = u*u*u; + float w2 = 3*u*u*t; + float w3 = 3*u*t*t; + float w4 = t * t *t; + float x = w1 * p1.x + w2 * p2.x + w3 * p3.x + w4 * p4.x; + float y = w1 * p1.y + w2 * p2.y + w3 * p3.y + w4 * p4.y; + zr_canvas_path_line_to(list, zr_vec2(x,y)); + } +} + +static void +zr_canvas_path_fill(struct zr_canvas *list, struct zr_color color) +{ + struct zr_vec2 *points; + ZR_ASSERT(list); + if (!list) return; + points = (struct zr_vec2*)zr_buffer_memory(list->buffer); + zr_canvas_add_poly_convex(list, points, list->path_count, color, list->shape_AA); + zr_canvas_path_clear(list); +} + +static void +zr_canvas_path_stroke(struct zr_canvas *list, struct zr_color color, + int closed, float thickness) +{ + struct zr_vec2 *points; + ZR_ASSERT(list); + if (!list) return; + points = (struct zr_vec2*)zr_buffer_memory(list->buffer); + zr_canvas_add_poly_line(list, points, list->path_count, color, + closed, thickness, list->line_AA); + zr_canvas_path_clear(list); +} + +static void +zr_canvas_add_line(struct zr_canvas *list, struct zr_vec2 a, + struct zr_vec2 b, struct zr_color col, float thickness) +{ + ZR_ASSERT(list); + if (!list || !col.a) return; + zr_canvas_path_line_to(list, zr_vec2_add(a, zr_vec2(0.5f, 0.5f))); + zr_canvas_path_line_to(list, zr_vec2_add(b, zr_vec2(0.5f, 0.5f))); + zr_canvas_path_stroke(list, col, ZR_STROKE_OPEN, thickness); +} + +static void +zr_canvas_add_rect(struct zr_canvas *list, struct zr_rect rect, + struct zr_color col, float rounding) +{ + ZR_ASSERT(list); + if (!list || !col.a) return; + zr_canvas_path_rect_to(list, zr_vec2(rect.x + 0.5f, rect.y + 0.5f), + zr_vec2(rect.x + rect.w + 0.5f, rect.y + rect.h + 0.5f), rounding); + zr_canvas_path_fill(list, col); +} + +static void +zr_canvas_add_triangle(struct zr_canvas *list, struct zr_vec2 a, + struct zr_vec2 b, struct zr_vec2 c, struct zr_color col) +{ + ZR_ASSERT(list); + if (!list || !col.a) return; + zr_canvas_path_line_to(list, a); + zr_canvas_path_line_to(list, b); + zr_canvas_path_line_to(list, c); + zr_canvas_path_fill(list, col); +} + +static void +zr_canvas_add_circle(struct zr_canvas *list, struct zr_vec2 center, + float radius, struct zr_color col, unsigned int segs) +{ + float a_max; + ZR_ASSERT(list); + if (!list || !col.a) return; + a_max = ZR_PI * 2.0f * ((float)segs - 1.0f) / (float)segs; + zr_canvas_path_arc_to(list, center, radius, 0.0f, a_max, segs); + zr_canvas_path_fill(list, col); +} + +static void +zr_canvas_add_curve(struct zr_canvas *list, struct zr_vec2 p0, + struct zr_vec2 cp0, struct zr_vec2 cp1, struct zr_vec2 p1, + struct zr_color col, unsigned int segments, float thickness) +{ + ZR_ASSERT(list); + if (!list || !col.a) return; + zr_canvas_path_line_to(list, p0); + zr_canvas_path_curve_to(list, cp0, cp1, p1, segments); + zr_canvas_path_stroke(list, col, ZR_STROKE_OPEN, thickness); +} + +static void +zr_canvas_push_rect_uv(struct zr_canvas *list, struct zr_vec2 a, + struct zr_vec2 c, struct zr_vec2 uva, struct zr_vec2 uvc, + struct zr_color color) +{ + zr_draw_vertex_color col = zr_color32(color); + struct zr_draw_vertex *vtx; + struct zr_vec2 uvb, uvd; + struct zr_vec2 b,d; + zr_draw_index *idx; + zr_draw_index index; + ZR_ASSERT(list); + if (!list) return; + + uvb = zr_vec2(uvc.x, uva.y); + uvd = zr_vec2(uva.x, uvc.y); + b = zr_vec2(c.x, a.y); + d = zr_vec2(a.x, c.y); + + index = (zr_draw_index)list->vertex_count; + vtx = zr_canvas_alloc_vertices(list, 4); + idx = zr_canvas_alloc_elements(list, 6); + if (!vtx || !idx) return; + + idx[0] = (zr_draw_index)(index+0); idx[1] = (zr_draw_index)(index+1); + idx[2] = (zr_draw_index)(index+2); idx[3] = (zr_draw_index)(index+0); + idx[4] = (zr_draw_index)(index+2); idx[5] = (zr_draw_index)(index+3); + + vtx[0] = zr_draw_vertex(a, uva, col); + vtx[1] = zr_draw_vertex(b, uvb, col); + vtx[2] = zr_draw_vertex(c, uvc, col); + vtx[3] = zr_draw_vertex(d, uvd, col); +} + +static void +zr_canvas_add_image(struct zr_canvas *list, struct zr_image texture, + struct zr_rect rect, struct zr_color color) +{ + ZR_ASSERT(list); + if (!list) return; + /* push new command with given texture */ + zr_canvas_push_image(list, texture.handle); + if (zr_image_is_subimage(&texture)) { + /* add region inside of the texture */ + struct zr_vec2 uv[2]; + uv[0].x = (float)texture.region[0]/(float)texture.w; + uv[0].y = (float)texture.region[1]/(float)texture.h; + uv[1].x = (float)(texture.region[0] + texture.region[2])/(float)texture.w; + uv[1].y = (float)(texture.region[1] + texture.region[3])/(float)texture.h; + zr_canvas_push_rect_uv(list, zr_vec2(rect.x, rect.y), + zr_vec2(rect.x + rect.w, rect.y + rect.h), uv[0], uv[1], color); + } else zr_canvas_push_rect_uv(list, zr_vec2(rect.x, rect.y), + zr_vec2(rect.x + rect.w, rect.y + rect.h), + zr_vec2(0.0f, 0.0f), zr_vec2(1.0f, 1.0f),color); +} + +static void +zr_canvas_add_text(struct zr_canvas *list, const struct zr_user_font *font, + struct zr_rect rect, const char *text, zr_size len, float font_height, + struct zr_color fg) +{ + float x; + zr_size text_len; + zr_rune unicode, next; + zr_size glyph_len, next_glyph_len; + struct zr_user_font_glyph g; + + ZR_ASSERT(list); + if (!list || !len || !text) return; + if (rect.x > (list->clip_rect.x + list->clip_rect.w) || + rect.y > (list->clip_rect.y + list->clip_rect.h) || + rect.x < list->clip_rect.x || rect.y < list->clip_rect.y) + return; + + /* draw every glyph image */ + zr_canvas_push_image(list, font->texture); + x = rect.x; + glyph_len = text_len = zr_utf_decode(text, &unicode, len); + if (!glyph_len) return; + while (text_len <= len && glyph_len) { + float gx, gy, gh, gw; + float char_width = 0; + if (unicode == ZR_UTF_INVALID) break; + + /* query currently drawn glyph information */ + next_glyph_len = zr_utf_decode(text + text_len, &next, len - text_len); + font->query(font->userdata, font_height, &g, unicode, + (next == ZR_UTF_INVALID) ? '\0' : next); + + /* calculate and draw glyph drawing rectangle and image */ + gx = x + g.offset.x; + /*gy = rect.y + (rect.h/2) - (font->height/2) + g.offset.y;*/ + gy = rect.y + g.offset.y; + gw = g.width; gh = g.height; + char_width = g.xadvance; + fg.a *= list->global_alpha; + zr_canvas_push_rect_uv(list, zr_vec2(gx,gy), zr_vec2(gx + gw, gy+ gh), + g.uv[0], g.uv[1], fg); + + /* offset next glyph */ + text_len += glyph_len; + x += char_width; + glyph_len = next_glyph_len; + unicode = next; + } +} + +static void +zr_canvas_load(struct zr_canvas *list, struct zr_context *queue, + float line_thickness, unsigned int curve_segments) +{ + const struct zr_command *cmd; + ZR_ASSERT(list); + ZR_ASSERT(list->vertices); + ZR_ASSERT(list->elements); + ZR_ASSERT(queue); + line_thickness = MAX(line_thickness, 1.0f); + if (!list || !queue || !list->vertices || !list->elements) return; + + zr_foreach(cmd, queue) + { +#if ZR_COMPILE_WITH_COMMAND_USERDATA + list->userdata = cmd->userdata; +#endif + switch (cmd->type) { + case ZR_COMMAND_NOP: break; + case ZR_COMMAND_SCISSOR: { + const struct zr_command_scissor *s = zr_command(scissor, cmd); + zr_canvas_add_clip(list, zr_rect(s->x, s->y, s->w, s->h)); + } break; + case ZR_COMMAND_LINE: { + const struct zr_command_line *l = zr_command(line, cmd); + zr_canvas_add_line(list, zr_vec2(l->begin.x, l->begin.y), + zr_vec2(l->end.x, l->end.y), l->color, line_thickness); + } break; + case ZR_COMMAND_CURVE: { + const struct zr_command_curve *q = zr_command(curve, cmd); + zr_canvas_add_curve(list, zr_vec2(q->begin.x, q->begin.y), + zr_vec2(q->ctrl[0].x, q->ctrl[0].y), zr_vec2(q->ctrl[1].x, + q->ctrl[1].y), zr_vec2(q->end.x, q->end.y), q->color, + curve_segments, line_thickness); + } break; + case ZR_COMMAND_RECT: { + const struct zr_command_rect *r = zr_command(rect, cmd); + zr_canvas_add_rect(list, zr_rect(r->x, r->y, r->w, r->h), + r->color, (float)r->rounding); + } break; + case ZR_COMMAND_CIRCLE: { + const struct zr_command_circle *c = zr_command(circle, cmd); + zr_canvas_add_circle(list, zr_vec2((float)c->x + (float)c->w/2, + (float)c->y + (float)c->h/2), (float)c->w/2, c->color, + curve_segments); + } break; + case ZR_COMMAND_ARC: { + const struct zr_command_arc *c = zr_command(arc, cmd); + zr_canvas_path_line_to(list, zr_vec2(c->cx, c->cy)); + zr_canvas_path_arc_to(list, zr_vec2(c->cx, c->cy), c->r, + c->a[0], c->a[1], curve_segments); + zr_canvas_path_fill(list, c->color); + } break; + case ZR_COMMAND_TRIANGLE: { + const struct zr_command_triangle *t = zr_command(triangle, cmd); + zr_canvas_add_triangle(list, zr_vec2(t->a.x, t->a.y), + zr_vec2(t->b.x, t->b.y), zr_vec2(t->c.x, t->c.y), t->color); + } break; + case ZR_COMMAND_TEXT: { + const struct zr_command_text *t = zr_command(text, cmd); + zr_canvas_add_text(list, t->font, zr_rect(t->x, t->y, t->w, t->h), + t->string, t->length, t->height, t->foreground); + } break; + case ZR_COMMAND_IMAGE: { + const struct zr_command_image *i = zr_command(image, cmd); + zr_canvas_add_image(list, i->img, zr_rect(i->x, i->y, i->w, i->h), + zr_rgb(255, 255, 255)); + } break; + default: break; + } + } +} + +void +zr_convert(struct zr_context *ctx, struct zr_buffer *cmds, + struct zr_buffer *vertices, struct zr_buffer *elements, + const struct zr_convert_config *config) +{ + zr_canvas_setup(&ctx->canvas, config->global_alpha, cmds, vertices, elements, + config->null, config->line_AA, config->shape_AA); + zr_canvas_load(&ctx->canvas, ctx, config->line_thickness, + config->circle_segment_count); +} + +const struct zr_draw_command* +zr__draw_begin(const struct zr_context *ctx, + const struct zr_buffer *buffer) +{ + zr_byte *memory; + zr_size offset; + const struct zr_draw_command *cmd; + ZR_ASSERT(buffer); + if (!buffer || !buffer->size || !ctx->canvas.cmd_count) return 0; + + memory = (zr_byte*)buffer->memory.ptr; + offset = buffer->memory.size - ctx->canvas.cmd_offset; + cmd = zr_ptr_add(const struct zr_draw_command, memory, offset); + return cmd; +} + +const struct zr_draw_command* +zr__draw_next(const struct zr_draw_command *cmd, + const struct zr_buffer *buffer, const struct zr_context *ctx) +{ + zr_byte *memory; + zr_size offset, size; + const struct zr_draw_command *end; + ZR_ASSERT(buffer); + ZR_ASSERT(ctx); + if (!cmd || !buffer || !ctx) return 0; + memory = (zr_byte*)buffer->memory.ptr; + size = buffer->memory.size; + offset = size - ctx->canvas.cmd_offset; + end = zr_ptr_add(const struct zr_draw_command, memory, offset); + end -= (ctx->canvas.cmd_count-1); + if (cmd <= end) return 0; + return (cmd-1); +} + +#endif +/* + * ============================================================== + * + * FONT + * + * =============================================================== + */ +#ifdef ZR_COMPILE_WITH_FONT + +/* this is a bloody mess but 'fixing' both stb libraries would be a pain */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wbad-function-cast" +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wshadow" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#pragma clang diagnostic ignored "-Wdeclaration-after-statement" +#pragma clang diagnostic ignored "-Wunused-function" +#elif defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wbad-function-cast" +#pragma GCC diagnostic ignored "-Wcast-qual" +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" +#pragma GCC diagnostic ignored "-Wtype-limits" +#pragma GCC diagnostic ignored "-Wswitch-default" +#pragma GCC diagnostic ignored "-Wunused-function" +#elif _MSC_VER +#pragma warning (push) +#pragma warning (disable: 4456) +#endif + +#if !ZR_DISABLE_STB_RECT_PACK_IMPLEMENTATION +#define STBRP_STATIC +#define STB_RECT_PACK_IMPLEMENTATION +#endif +#include "stb_rect_pack.h" + +#if !ZR_DISABLE_STB_TRUETYPE_IMPLEMENTATION +#define STBTT_STATIC +#define STB_TRUETYPE_IMPLEMENTATION +#endif +#include "stb_truetype.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) || defined(__GNUG__) +#pragma GCC diagnostic pop +#elif _MSC_VER +#pragma warning (pop) +#endif + +struct zr_font_bake_data { + stbtt_fontinfo info; + stbrp_rect *rects; + stbtt_pack_range *ranges; + zr_rune range_count; +}; + +struct zr_font_baker { + stbtt_pack_context spc; + struct zr_font_bake_data *build; + stbtt_packedchar *packed_chars; + stbrp_rect *rects; + stbtt_pack_range *ranges; +}; + +static const zr_size zr_rect_align = ZR_ALIGNOF(stbrp_rect); +static const zr_size zr_range_align = ZR_ALIGNOF(stbtt_pack_range); +static const zr_size zr_char_align = ZR_ALIGNOF(stbtt_packedchar); +static const zr_size zr_build_align = ZR_ALIGNOF(struct zr_font_bake_data); +static const zr_size zr_baker_align = ZR_ALIGNOF(struct zr_font_baker); + +static int +zr_range_count(const zr_rune *range) +{ + const zr_rune *iter = range; + ZR_ASSERT(range); + if (!range) return 0; + while (*(iter++) != 0); + return (iter == range) ? 0 : (int)((iter - range)/2); +} + +static int +zr_range_glyph_count(const zr_rune *range, int count) +{ + int i = 0; + int total_glyphs = 0; + for (i = 0; i < count; ++i) { + int diff; + zr_rune f = range[(i*2)+0]; + zr_rune t = range[(i*2)+1]; + ZR_ASSERT(t >= f); + diff = (int)((t - f) + 1); + total_glyphs += diff; + } + return total_glyphs; +} + +const zr_rune* +zr_font_default_glyph_ranges(void) +{ + static const zr_rune ranges[] = {0x0020, 0x00FF, 0}; + return ranges; +} + +const zr_rune* +zr_font_chinese_glyph_ranges(void) +{ + static const zr_rune ranges[] = { + 0x0020, 0x00FF, + 0x3000, 0x30FF, + 0x31F0, 0x31FF, + 0xFF00, 0xFFEF, + 0x4e00, 0x9FAF, + 0 + }; + return ranges; +} + +const zr_rune* +zr_font_cyrillic_glyph_ranges(void) +{ + static const zr_rune ranges[] = { + 0x0020, 0x00FF, + 0x0400, 0x052F, + 0x2DE0, 0x2DFF, + 0xA640, 0xA69F, + 0 + }; + return ranges; +} + +const zr_rune* +zr_font_korean_glyph_ranges(void) +{ + static const zr_rune ranges[] = { + 0x0020, 0x00FF, + 0x3131, 0x3163, + 0xAC00, 0xD79D, + 0 + }; + return ranges; +} + +void +zr_font_bake_memory(zr_size *temp, int *glyph_count, + struct zr_font_config *config, int count) +{ + int i, range_count = 0; + ZR_ASSERT(config); + ZR_ASSERT(glyph_count); + if (!config) { + *temp = 0; + *glyph_count = 0; + return; + } + + *glyph_count = 0; + if (!config->range) + config->range = zr_font_default_glyph_ranges(); + for (i = 0; i < count; ++i) { + range_count += zr_range_count(config[i].range); + *glyph_count += zr_range_glyph_count(config[i].range, range_count); + } + + *temp = (zr_size)*glyph_count * sizeof(stbrp_rect); + *temp += (zr_size)range_count * sizeof(stbtt_pack_range); + *temp += (zr_size)*glyph_count * sizeof(stbtt_packedchar); + *temp += (zr_size)count * sizeof(struct zr_font_bake_data); + *temp += sizeof(struct zr_font_baker); + *temp += zr_rect_align + zr_range_align + zr_char_align; + *temp += zr_build_align + zr_baker_align; +} + +static struct zr_font_baker* +zr_font_baker(void *memory, int glyph_count, int count) +{ + struct zr_font_baker *baker; + if (!memory) return 0; + /* setup baker inside a memory block */ + baker = (struct zr_font_baker*)ZR_ALIGN_PTR(memory, zr_baker_align); + baker->build = (struct zr_font_bake_data*)ZR_ALIGN_PTR((baker + 1), zr_build_align); + baker->packed_chars = (stbtt_packedchar*)ZR_ALIGN_PTR((baker->build + count), zr_char_align); + baker->rects = (stbrp_rect*)ZR_ALIGN_PTR((baker->packed_chars + glyph_count), zr_rect_align); + baker->ranges = (stbtt_pack_range*)ZR_ALIGN_PTR((baker->rects + glyph_count), zr_range_align); + return baker; +} + +int +zr_font_bake_pack(zr_size *image_memory, int *width, int *height, + struct zr_recti *custom, void *temp, zr_size temp_size, + const struct zr_font_config *config, int count) +{ + static const zr_size max_height = 1024 * 32; + struct zr_font_baker* baker; + int total_glyph_count = 0; + int total_range_count = 0; + int i = 0; + + ZR_ASSERT(image_memory); + ZR_ASSERT(width); + ZR_ASSERT(height); + ZR_ASSERT(config); + ZR_ASSERT(temp); + ZR_ASSERT(temp_size); + ZR_ASSERT(count); + if (!image_memory || !width || !height || !config || !temp || + !temp_size || !count) return zr_false; + + for (i = 0; i < count; ++i) { + total_range_count += zr_range_count(config[i].range); + total_glyph_count += zr_range_glyph_count(config[i].range, total_range_count); + } + + /* setup font baker from temporary memory */ + zr_zero(temp, temp_size); + baker = zr_font_baker(temp, total_glyph_count, count); + if (!baker) return zr_false; + for (i = 0; i < count; ++i) { + const struct zr_font_config *cfg = &config[i]; + if (!stbtt_InitFont(&baker->build[i].info, (const unsigned char*)cfg->ttf_blob, 0)) + return zr_false; + } + + *height = 0; + *width = (total_glyph_count > 1000) ? 1024 : 512; + stbtt_PackBegin(&baker->spc, 0, (int)*width, (int)max_height, 0, 1, 0); + { + int input_i = 0; + int range_n = 0, rect_n = 0, char_n = 0; + + /* pack custom user data first so it will be in the upper left corner*/ + if (custom) { + stbrp_rect custom_space; + zr_zero(&custom_space, sizeof(custom_space)); + custom_space.w = (stbrp_coord)((custom->w * 2) + 1); + custom_space.h = (stbrp_coord)(custom->h + 1); + + stbtt_PackSetOversampling(&baker->spc, 1, 1); + stbrp_pack_rects((stbrp_context*)baker->spc.pack_info, &custom_space, 1); + *height = MAX(*height, (int)(custom_space.y + custom_space.h)); + + custom->x = (short)custom_space.x; + custom->y = (short)custom_space.y; + custom->w = (short)custom_space.w; + custom->h = (short)custom_space.h; + } + + /* first font pass: pack all glyphs */ + for (input_i = 0; input_i < count; input_i++) { + int n = 0; + const zr_rune *in_range; + const struct zr_font_config *cfg = &config[input_i]; + struct zr_font_bake_data *tmp = &baker->build[input_i]; + int glyph_count, range_count; + + /* count glyphs + ranges in current font */ + glyph_count = 0; range_count = 0; + for (in_range = cfg->range; in_range[0] && in_range[1]; in_range += 2) { + glyph_count += (int)(in_range[1] - in_range[0]) + 1; + range_count++; + } + + /* setup ranges */ + tmp->ranges = baker->ranges + range_n; + tmp->range_count = (zr_rune)range_count; + range_n += range_count; + for (i = 0; i < range_count; ++i) { + in_range = &cfg->range[i * 2]; + tmp->ranges[i].font_size = cfg->size; + tmp->ranges[i].first_unicode_codepoint_in_range = (int)in_range[0]; + tmp->ranges[i].num_chars = (int)(in_range[1]- in_range[0]) + 1; + tmp->ranges[i].chardata_for_range = baker->packed_chars + char_n; + char_n += tmp->ranges[i].num_chars; + } + + /* pack */ + tmp->rects = baker->rects + rect_n; + rect_n += glyph_count; + stbtt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v); + n = stbtt_PackFontRangesGatherRects(&baker->spc, &tmp->info, + tmp->ranges, (int)tmp->range_count, tmp->rects); + stbrp_pack_rects((stbrp_context*)baker->spc.pack_info, tmp->rects, (int)n); + + /* texture height */ + for (i = 0; i < n; ++i) { + if (tmp->rects[i].was_packed) + *height = MAX(*height, tmp->rects[i].y + tmp->rects[i].h); + } + } + ZR_ASSERT(rect_n == total_glyph_count); + ZR_ASSERT(char_n == total_glyph_count); + ZR_ASSERT(range_n == total_range_count); + } + *height = (int)zr_round_up_pow2((zr_uint)*height); + *image_memory = (zr_size)(*width) * (zr_size)(*height); + return zr_true; +} + +void +zr_font_bake(void *image_memory, int width, int height, + void *temp, zr_size temp_size, struct zr_font_glyph *glyphs, + int glyphs_count, const struct zr_font_config *config, int font_count) +{ + int input_i = 0; + struct zr_font_baker* baker; + zr_rune glyph_n = 0; + + ZR_ASSERT(image_memory); + ZR_ASSERT(width); + ZR_ASSERT(height); + ZR_ASSERT(config); + ZR_ASSERT(temp); + ZR_ASSERT(temp_size); + ZR_ASSERT(font_count); + ZR_ASSERT(glyphs_count); + if (!image_memory || !width || !height || !config || !temp || + !temp_size || !font_count || !glyphs || !glyphs_count) + return; + + /* second font pass: render glyphs */ + baker = (struct zr_font_baker*)ZR_ALIGN_PTR(temp, zr_baker_align); + zr_zero(image_memory, (zr_size)((zr_size)width * (zr_size)height)); + baker->spc.pixels = (unsigned char*)image_memory; + baker->spc.height = (int)height; + for (input_i = 0; input_i < font_count; ++input_i) { + const struct zr_font_config *cfg = &config[input_i]; + struct zr_font_bake_data *tmp = &baker->build[input_i]; + stbtt_PackSetOversampling(&baker->spc, cfg->oversample_h, cfg->oversample_v); + stbtt_PackFontRangesRenderIntoRects(&baker->spc, &tmp->info, tmp->ranges, + (int)tmp->range_count, tmp->rects); + } + stbtt_PackEnd(&baker->spc); + + /* third pass: setup font and glyphs */ + for (input_i = 0; input_i < font_count; ++input_i) { + zr_size i = 0; + int char_idx = 0; + zr_rune glyph_count = 0; + const struct zr_font_config *cfg = &config[input_i]; + struct zr_font_bake_data *tmp = &baker->build[input_i]; + struct zr_baked_font *dst_font = cfg->font; + + float font_scale = stbtt_ScaleForPixelHeight(&tmp->info, cfg->size); + int unscaled_ascent, unscaled_descent, unscaled_line_gap; + stbtt_GetFontVMetrics(&tmp->info, &unscaled_ascent, &unscaled_descent, + &unscaled_line_gap); + + /* fill baked font */ + dst_font->ranges = cfg->range; + dst_font->height = cfg->size; + dst_font->ascent = ((float)unscaled_ascent * font_scale); + dst_font->descent = ((float)unscaled_descent * font_scale); + dst_font->glyph_offset = glyph_n; + + /* fill own baked font glyph array */ + for (i = 0; i < tmp->range_count; ++i) { + stbtt_pack_range *range = &tmp->ranges[i]; + for (char_idx = 0; char_idx < range->num_chars; char_idx++) { + zr_rune codepoint = 0; + float dummy_x = 0, dummy_y = 0; + stbtt_aligned_quad q; + struct zr_font_glyph *glyph; + + /* query glyph bounds from stb_truetype */ + const stbtt_packedchar *pc = &range->chardata_for_range[char_idx]; + glyph_count++; + if (!pc->x0 && !pc->x1 && !pc->y0 && !pc->y1) continue; + codepoint = (zr_rune)(range->first_unicode_codepoint_in_range + char_idx); + stbtt_GetPackedQuad(range->chardata_for_range, (int)width, + (int)height, char_idx, &dummy_x, &dummy_y, &q, 0); + + /* fill own glyph type with data */ + glyph = &glyphs[dst_font->glyph_offset + (unsigned int)char_idx]; + glyph->codepoint = codepoint; + glyph->x0 = q.x0; glyph->y0 = q.y0; + glyph->x1 = q.x1; glyph->y1 = q.y1; + glyph->y0 += (dst_font->ascent + 0.5f); + glyph->y1 += (dst_font->ascent + 0.5f); + glyph->w = glyph->x1 - glyph->x0 + 0.5f; + glyph->h = glyph->y1 - glyph->y0; + + if (cfg->coord_type == ZR_COORD_PIXEL) { + glyph->u0 = q.s0 * (float)width; + glyph->v0 = q.t0 * (float)height; + glyph->u1 = q.s1 * (float)width; + glyph->v1 = q.t1 * (float)height; + } else { + glyph->u0 = q.s0; + glyph->v0 = q.t0; + glyph->u1 = q.s1; + glyph->v1 = q.t1; + } + glyph->xadvance = (pc->xadvance + cfg->spacing.x); + if (cfg->pixel_snap) + glyph->xadvance = (float)(int)(glyph->xadvance + 0.5f); + } + } + dst_font->glyph_count = glyph_count; + glyph_n += dst_font->glyph_count; + } +} + +void +zr_font_bake_custom_data(void *img_memory, int img_width, int img_height, + struct zr_recti img_dst, const char *texture_data_mask, int tex_width, + int tex_height, char white, char black) +{ + zr_byte *pixels; + int y = 0, x = 0, n = 0; + ZR_ASSERT(img_memory); + ZR_ASSERT(img_width); + ZR_ASSERT(img_height); + ZR_ASSERT(texture_data_mask); + ZR_UNUSED(tex_height); + if (!img_memory || !img_width || !img_height || !texture_data_mask) + return; + + pixels = (zr_byte*)img_memory; + for (y = 0, n = 0; y < tex_height; ++y) { + for (x = 0; x < tex_width; ++x, ++n) { + const int off0 = ((img_dst.x + x) + (img_dst.y + y) * img_width); + const int off1 = off0 + 1 + tex_width; + pixels[off0] = (texture_data_mask[n] == white) ? 0xFF : 0x00; + pixels[off1] = (texture_data_mask[n] == black) ? 0xFF : 0x00; + } + } +} + +void +zr_font_bake_convert(void *out_memory, int img_width, int img_height, + const void *in_memory) +{ + int n = 0; + const zr_byte *src; + zr_rune *dst; + ZR_ASSERT(out_memory); + ZR_ASSERT(in_memory); + ZR_ASSERT(img_width); + ZR_ASSERT(img_height); + if (!out_memory || !in_memory || !img_height || !img_width) return; + + dst = (zr_rune*)out_memory; + src = (const zr_byte*)in_memory; + for (n = (int)(img_width * img_height); n > 0; n--) + *dst++ = ((zr_rune)(*src++) << 24) | 0x00FFFFFF; +} +/* ------------------------------------------------------------- + * + * FONT + * + * --------------------------------------------------------------*/ +void +zr_font_init(struct zr_font *font, float pixel_height, + zr_rune fallback_codepoint, struct zr_font_glyph *glyphs, + const struct zr_baked_font *baked_font, zr_handle atlas) +{ + ZR_ASSERT(font); + ZR_ASSERT(glyphs); + ZR_ASSERT(baked_font); + if (!font || !glyphs || !baked_font) + return; + + zr_zero(font, sizeof(*font)); + font->ascent = baked_font->ascent; + font->descent = baked_font->descent; + font->size = baked_font->height; + font->scale = (float)pixel_height / (float)font->size; + font->glyphs = &glyphs[baked_font->glyph_offset]; + font->glyph_count = baked_font->glyph_count; + font->ranges = baked_font->ranges; + font->atlas = atlas; + font->fallback_codepoint = fallback_codepoint; + font->fallback = zr_font_find_glyph(font, fallback_codepoint); +} + +const struct zr_font_glyph* +zr_font_find_glyph(struct zr_font *font, zr_rune unicode) +{ + int i = 0; + int count; + int total_glyphs = 0; + const struct zr_font_glyph *glyph = 0; + ZR_ASSERT(font); + + glyph = font->fallback; + count = zr_range_count(font->ranges); + for (i = 0; i < count; ++i) { + int diff; + zr_rune f = font->ranges[(i*2)+0]; + zr_rune t = font->ranges[(i*2)+1]; + diff = (int)((t - f) + 1); + if (unicode >= f && unicode <= t) + return &font->glyphs[((zr_rune)total_glyphs + (unicode - f))]; + total_glyphs += diff; + } + return glyph; +} + +static zr_size +zr_font_text_width(zr_handle handle, float height, const char *text, zr_size len) +{ + zr_rune unicode; + zr_size text_len = 0; + float text_width = 0; + zr_size glyph_len = 0; + float scale = 0; + + struct zr_font *font = (struct zr_font*)handle.ptr; + ZR_ASSERT(font); + if (!font || !text || !len) + return 0; + + scale = height/font->size; + glyph_len = text_len = zr_utf_decode(text, &unicode, len); + if (!glyph_len) return 0; + while (text_len <= len && glyph_len) { + const struct zr_font_glyph *g; + if (unicode == ZR_UTF_INVALID) break; + + /* query currently drawn glyph information */ + g = zr_font_find_glyph(font, unicode); + text_width += g->xadvance * scale; + + /* offset next glyph */ + glyph_len = zr_utf_decode(text + text_len, &unicode, len - text_len); + text_len += glyph_len; + } + return (zr_size)text_width; +} + +#if ZR_COMPILE_WITH_VERTEX_BUFFER +static void +zr_font_query_font_glyph(zr_handle handle, float height, + struct zr_user_font_glyph *glyph, zr_rune codepoint, zr_rune next_codepoint) +{ + float scale; + const struct zr_font_glyph *g; + struct zr_font *font; + ZR_ASSERT(glyph); + ZR_UNUSED(next_codepoint); + font = (struct zr_font*)handle.ptr; + ZR_ASSERT(font); + if (!font || !glyph) + return; + + scale = height/font->size; + g = zr_font_find_glyph(font, codepoint); + glyph->width = (g->x1 - g->x0) * scale; + glyph->height = (g->y1 - g->y0) * scale; + glyph->offset = zr_vec2(g->x0 * scale, g->y0 * scale); + glyph->xadvance = (g->xadvance * scale); + glyph->uv[0] = zr_vec2(g->u0, g->v0); + glyph->uv[1] = zr_vec2(g->u1, g->v1); +} +#endif + +struct zr_user_font +zr_font_ref(struct zr_font *font) +{ + struct zr_user_font user_font; + zr_zero(&user_font, sizeof(user_font)); + user_font.height = font->size * font->scale; + user_font.width = zr_font_text_width; + user_font.userdata.ptr = font; +#if ZR_COMPILE_WITH_VERTEX_BUFFER + user_font.query = zr_font_query_font_glyph; + user_font.texture = font->atlas; +#endif + return user_font; +} +#endif +/* + * ============================================================== + * + * Edit Box + * + * =============================================================== + */ +static void +zr_edit_buffer_append(struct zr_buffer *buffer, const char *str, zr_size len) +{ + char *mem; + ZR_ASSERT(buffer); + ZR_ASSERT(str); + if (!buffer || !str || !len) return; + mem = (char*)zr_buffer_alloc(buffer, ZR_BUFFER_FRONT, len * sizeof(char), 0); + if (!mem) return; + zr_memcopy(mem, str, len * sizeof(char)); +} + +static int +zr_edit_buffer_insert(struct zr_buffer *buffer, zr_size pos, + const char *str, zr_size len) +{ + void *mem; + zr_size i; + char *src, *dst; + + zr_size copylen; + ZR_ASSERT(buffer); + if (!buffer || !str || !len || pos > buffer->allocated) return 0; + if ((buffer->allocated + len >= buffer->memory.size) && + (buffer->type == ZR_BUFFER_FIXED)) return 0; + + copylen = buffer->allocated - pos; + if (!copylen) { + zr_edit_buffer_append(buffer, str, len); + return 1; + } + mem = zr_buffer_alloc(buffer, ZR_BUFFER_FRONT, len * sizeof(char), 0); + if (!mem) return 0; + + /* memmove */ + ZR_ASSERT(((int)pos + (int)len + ((int)copylen - 1)) >= 0); + ZR_ASSERT(((int)pos + ((int)copylen - 1)) >= 0); + dst = zr_ptr_add(char, buffer->memory.ptr, pos + len + (copylen - 1)); + src = zr_ptr_add(char, buffer->memory.ptr, pos + (copylen-1)); + for (i = 0; i < copylen; ++i) *dst-- = *src--; + mem = zr_ptr_add(void, buffer->memory.ptr, pos); + zr_memcopy(mem, str, len * sizeof(char)); + return 1; +} + +static void +zr_edit_buffer_remove(struct zr_buffer *buffer, zr_size len) +{ + ZR_ASSERT(buffer); + if (!buffer || len > buffer->allocated) return; + ZR_ASSERT(((int)buffer->allocated - (int)len) >= 0); + buffer->allocated -= len; +} + +static void +zr_edit_buffer_del(struct zr_buffer *buffer, zr_size pos, zr_size len) +{ + ZR_ASSERT(buffer); + if (!buffer || !len || pos > buffer->allocated || + pos + len > buffer->allocated) return; + + if (pos + len < buffer->allocated) { + /* memmove */ + char *dst = zr_ptr_add(char, buffer->memory.ptr, pos); + char *src = zr_ptr_add(char, buffer->memory.ptr, pos + len); + zr_memcopy(dst, src, buffer->allocated - (pos + len)); + ZR_ASSERT(((int)buffer->allocated - (int)len) >= 0); + buffer->allocated -= len; + } else zr_edit_buffer_remove(buffer, len); +} + +static char* +zr_edit_buffer_at_char(struct zr_buffer *buffer, zr_size pos) +{ + ZR_ASSERT(buffer); + if (!buffer || pos > buffer->allocated) return 0; + return zr_ptr_add(char, buffer->memory.ptr, pos); +} + +static char* +zr_edit_buffer_at(struct zr_buffer *buffer, int pos, zr_rune *unicode, + zr_size *len) +{ + int i = 0; + zr_size src_len = 0; + zr_size glyph_len = 0; + char *text; + zr_size text_len; + + ZR_ASSERT(buffer); + ZR_ASSERT(unicode); + ZR_ASSERT(len); + if (!buffer || !unicode || !len) return 0; + if (pos < 0) { + *unicode = 0; + *len = 0; + return 0; + } + + text = (char*)buffer->memory.ptr; + text_len = buffer->allocated; + glyph_len = zr_utf_decode(text, unicode, text_len); + while (glyph_len) { + if (i == pos) { + *len = glyph_len; + break; + } + + i++; + src_len = src_len + glyph_len; + glyph_len = zr_utf_decode(text + src_len, unicode, text_len - src_len); + } + if (i != pos) return 0; + return text + src_len; +} + +static void +zr_edit_box_init_buffer(struct zr_edit_box *eb, struct zr_buffer *buffer, + const struct zr_clipboard *clip, zr_filter f) +{ + ZR_ASSERT(eb); + if (!eb) return; + zr_zero(eb, sizeof(*eb)); + eb->buffer = *buffer; + if (clip) eb->clip = *clip; + if (f) eb->filter = f; + else eb->filter = zr_filter_default; + eb->cursor = 0; + eb->glyphs = 0; +} + +static void +zr_edit_box_init(struct zr_edit_box *eb, void *memory, zr_size size, + const struct zr_clipboard *clip, zr_filter f) +{ + ZR_ASSERT(eb); + if (!eb) return; + zr_zero(eb, sizeof(*eb)); + zr_buffer_init_fixed(&eb->buffer, memory, size); + if (clip) eb->clip = *clip; + if (f) eb->filter = f; + else eb->filter = zr_filter_default; + eb->cursor = 0; + eb->glyphs = 0; +} + +void +zr_edit_box_clear(struct zr_edit_box *box) +{ + ZR_ASSERT(box); + if (!box) return; + zr_buffer_clear(&box->buffer); + box->cursor = box->glyphs = 0; +} + +void +zr_edit_box_add(struct zr_edit_box *eb, const char *str, zr_size len) +{ + int res = 0; + ZR_ASSERT(eb); + if (!eb || !str || !len) return; + + if (eb->cursor != eb->glyphs) { + zr_size l = 0; + zr_rune unicode; + int cursor = (eb->cursor) ? (int)(eb->cursor) : 0; + char *sym = zr_edit_buffer_at(&eb->buffer, cursor, &unicode, &l); + zr_size offset = (zr_size)(sym - (char*)eb->buffer.memory.ptr); + res = zr_edit_buffer_insert(&eb->buffer, offset, str, len); + } else res = zr_edit_buffer_insert(&eb->buffer, eb->buffer.allocated, str, len); + + if (res) { + zr_size l = zr_utf_len(str, len); + eb->glyphs += l; + eb->cursor += l; + eb->text_inserted = 1; + } +} + +static zr_size +zr_edit_box_buffer_input(struct zr_edit_box *box, const struct zr_input *i) +{ + zr_rune unicode; + zr_size src_len = 0; + zr_size text_len = 0, glyph_len = 0; + zr_size glyphs = 0; + + ZR_ASSERT(box); + ZR_ASSERT(i); + if (!box || !i) return 0; + + /* add user provided text to buffer until either no input or buffer space left*/ + glyph_len = zr_utf_decode(i->keyboard.text, &unicode, i->keyboard.text_len); + while (glyph_len && ((text_len+glyph_len) <= i->keyboard.text_len)) { + /* filter to make sure the value is correct */ + if (box->filter(box, unicode)) { + zr_edit_box_add(box, &i->keyboard.text[text_len], glyph_len); + text_len += glyph_len; + glyphs++; + } + src_len = src_len + glyph_len; + glyph_len = zr_utf_decode(i->keyboard.text + src_len, &unicode, + i->keyboard.text_len - src_len); + } + return glyphs; +} + +void +zr_edit_box_remove(struct zr_edit_box *box, enum zr_edit_remove_operation op) +{ + zr_size len; + char *buf; + zr_size min, maxi, diff; + ZR_ASSERT(box); + if (!box) return; + if (!box->glyphs) return; + + buf = (char*)box->buffer.memory.ptr; + min = MIN(box->sel.end, box->sel.begin); + maxi = MAX(box->sel.end, box->sel.begin); + diff = MAX(1, maxi - min); + + if (diff && box->cursor != box->glyphs) { + zr_size off; + zr_rune unicode; + char *begin, *end; + + /* calculate text selection byte position and size */ + begin = zr_edit_buffer_at(&box->buffer, (int)min, &unicode, &len); + end = zr_edit_buffer_at(&box->buffer, (int)maxi, &unicode, &len); + len = MAX((zr_size)(end - begin), 1); + off = (zr_size)(begin - buf); + + /* delete text selection */ + if (len > 1) { + zr_edit_buffer_del(&box->buffer, off, len); + } else { + if (op == ZR_DELETE) { + zr_edit_buffer_del(&box->buffer, off, len); + } else { + zr_edit_buffer_del(&box->buffer, off-1, len); + box->cursor = (box->cursor) ? box->cursor-1: 0; + box->sel.begin = box->cursor; + box->sel.end = box->cursor; + } + } + box->glyphs = zr_utf_len(buf, box->buffer.allocated); + } else if (op == ZR_REMOVE) { + zr_rune unicode; + int cursor; + char *glyph; + zr_size offset; + + /* remove last glyph */ + cursor = (int)box->cursor - 1; + glyph = zr_edit_buffer_at(&box->buffer, cursor, &unicode, &len); + if (!glyph || !len) return; + + offset = (zr_size)(glyph - (char*)box->buffer.memory.ptr); + zr_edit_buffer_del(&box->buffer, offset, len); + box->glyphs--; + } + if (op == ZR_DELETE) { + if (box->cursor >= box->glyphs) + box->cursor = box->glyphs; + else if (box->cursor > 0) + box->cursor--; + } +} + +int +zr_edit_box_has_selection(const struct zr_edit_box *eb) +{ + ZR_ASSERT(eb); + if (!eb) return 0; + return (int)(eb->sel.end - eb->sel.begin); +} + +const char* +zr_edit_box_get_selection(zr_size *len, struct zr_edit_box *eb) +{ + zr_size l; + const char *begin; + zr_rune unicode; + ZR_ASSERT(eb); + ZR_ASSERT(len); + if (!eb || !len || !(eb->sel.end - eb->sel.begin)) + return 0; + + begin = zr_edit_buffer_at(&eb->buffer, (int)eb->sel.begin, &unicode, &l); + *len = (eb->sel.end - eb->sel.begin) + 1; + return begin; +} + +char* +zr_edit_box_get(struct zr_edit_box *eb) +{ + ZR_ASSERT(eb); + if (!eb) return 0; + return (char*)eb->buffer.memory.ptr; +} + +const char* +zr_edit_box_get_const(const struct zr_edit_box *eb) +{ + ZR_ASSERT(eb); + if (!eb) return 0; + return (char*)eb->buffer.memory.ptr; +} + +char +zr_edit_box_at_char(struct zr_edit_box *eb, zr_size pos) +{ + char *c; + ZR_ASSERT(eb); + if (!eb || pos >= eb->buffer.allocated) return 0; + c = zr_edit_buffer_at_char(&eb->buffer, pos); + return c ? *c : 0; +} + +void +zr_edit_box_at(struct zr_edit_box *eb, zr_size pos, zr_glyph g, zr_size *len) +{ + char *sym; + zr_rune unicode; + + ZR_ASSERT(eb); + ZR_ASSERT(g); + ZR_ASSERT(len); + + if (!eb || !len || !g) return; + if (pos >= eb->glyphs) { + *len = 0; + return; + } + sym = zr_edit_buffer_at(&eb->buffer, (int)pos, &unicode, len); + if (!sym) return; + zr_memcopy(g, sym, *len); +} + +void +zr_edit_box_at_cursor(struct zr_edit_box *eb, zr_glyph g, zr_size *len) +{ + const char *text; + zr_size text_len; + zr_size glyph_len = 0; + + ZR_ASSERT(eb); + ZR_ASSERT(g); + ZR_ASSERT(len); + + if (!eb || !g || !len) return; + if (!eb->cursor) { + *len = 0; + return; + } + + text = (char*)eb->buffer.memory.ptr; + text_len = eb->buffer.allocated; + glyph_len = zr_utf_len(text, text_len); + zr_edit_box_at(eb, glyph_len, g, len); +} + +void +zr_edit_box_set_cursor(struct zr_edit_box *eb, zr_size pos) +{ + ZR_ASSERT(eb); + ZR_ASSERT(eb->glyphs >= pos); + if (!eb || pos > eb->glyphs) return; + eb->cursor = pos; +} + +zr_size +zr_edit_box_get_cursor(struct zr_edit_box *eb) +{ + ZR_ASSERT(eb); + if (!eb) return 0; + return eb->cursor; +} + +zr_size +zr_edit_box_len_char(struct zr_edit_box *eb) +{ + ZR_ASSERT(eb); + return eb->buffer.allocated; +} + +zr_size +zr_edit_box_len(struct zr_edit_box *eb) +{ + ZR_ASSERT(eb); + return eb->glyphs; +} + +/* =============================================================== + * + * TEXT + * + * ===============================================================*/ +struct zr_text { + struct zr_vec2 padding; + struct zr_color background; + struct zr_color text; +}; + +static void +zr_widget_text(struct zr_command_buffer *o, struct zr_rect b, + const char *string, zr_size len, const struct zr_text *t, + zr_flags a, const struct zr_user_font *f) +{ + struct zr_rect label; + zr_size text_width; + + ZR_ASSERT(o); + ZR_ASSERT(t); + if (!o || !t) return; + + b.h = MAX(b.h, 2 * t->padding.y); + label.x = 0; label.w = 0; + label.y = b.y + t->padding.y; + label.h = b.h - 2 * t->padding.y; + + text_width = f->width(f->userdata, f->height, (const char*)string, len); + text_width += (zr_size)(2 * t->padding.x); + + /* align in x-axis */ + if (a & ZR_TEXT_LEFT) { + label.x = b.x + t->padding.x; + label.w = MAX(0, b.w - 2 * t->padding.x); + } else if (a & ZR_TEXT_CENTERED) { + label.w = MAX(1, 2 * t->padding.x + (float)text_width); + label.x = (b.x + t->padding.x + ((b.w - 2 * t->padding.x) - label.w) / 2); + label.x = MAX(b.x + t->padding.x, label.x); + label.w = MIN(b.x + b.w, label.x + label.w); + if (label.w >= label.x) label.w -= label.x; + } else if (a & ZR_TEXT_RIGHT) { + label.x = MAX(b.x + t->padding.x, (b.x + b.w) - (2 * t->padding.x + (float)text_width)); + label.w = (float)text_width + 2 * t->padding.x; + } else return; + + /* align in y-axis */ + if (a & ZR_TEXT_MIDDLE) { + label.y = b.y + b.h/2.0f - (float)f->height/2.0f; + label.h = b.h - (b.h/2.0f + f->height/2.0f); + } else if (a & ZR_TEXT_BOTTOM) { + label.y = b.y + b.h - f->height; + label.h = f->height; + } + zr_draw_text(o, label, (const char*)string, + len, f, t->background, t->text); +} + +static void +zr_widget_text_wrap(struct zr_command_buffer *o, struct zr_rect b, + const char *string, zr_size len, const struct zr_text *t, + const struct zr_user_font *f) +{ + float width; + zr_size glyphs = 0; + zr_size fitting = 0; + zr_size done = 0; + struct zr_rect line; + struct zr_text text; + + ZR_ASSERT(o); + ZR_ASSERT(t); + if (!o || !t) return; + + text.padding = zr_vec2(0,0); + text.background = t->background; + text.text = t->text; + + b.w = MAX(b.w, 2 * t->padding.x); + b.h = MAX(b.h, 2 * t->padding.y); + b.h = b.h - 2 * t->padding.y; + + line.x = b.x + t->padding.x; + line.y = b.y + t->padding.y; + line.w = b.w - 2 * t->padding.x; + line.h = 2 * t->padding.y + f->height; + + fitting = zr_use_font_glyph_clamp(f, string, len, line.w, &glyphs, &width); + while (done < len) { + if (!fitting || line.y + line.h >= (b.y + b.h)) break; + zr_widget_text(o, line, &string[done], fitting, &text, ZR_TEXT_LEFT, f); + done += fitting; + line.y += f->height + 2 * t->padding.y; + fitting = zr_use_font_glyph_clamp(f, &string[done], len - done, + line.w, &glyphs, &width); + } +} + +/* =============================================================== + * + * BUTTON + * + * ===============================================================*/ +struct zr_button { + float border_width; + float rounding; + struct zr_vec2 padding; + struct zr_vec2 touch_pad; + struct zr_color border; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; +}; + +struct zr_button_text { + struct zr_button base; + zr_flags alignment; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; +}; + +struct zr_button_symbol { + struct zr_button base; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; +}; + +struct zr_button_icon { + struct zr_button base; + struct zr_vec2 padding; +}; + +struct zr_symbol { + enum zr_symbol_type type; + struct zr_color background; + struct zr_color foreground; + float border_width; +}; + +static void +zr_draw_symbol(struct zr_command_buffer *out, const struct zr_symbol *sym, + struct zr_rect content, const struct zr_user_font *font) +{ + switch (sym->type) { + case ZR_SYMBOL_X: + case ZR_SYMBOL_UNDERSCORE: + case ZR_SYMBOL_PLUS: + case ZR_SYMBOL_MINUS: { + /* single character text symbol */ + const char *X = (sym->type == ZR_SYMBOL_X) ? "x": + (sym->type == ZR_SYMBOL_UNDERSCORE) ? "_": + (sym->type == ZR_SYMBOL_PLUS) ? "+": "-"; + struct zr_text text; + text.padding = zr_vec2(0,0); + text.background = sym->background; + text.text = sym->foreground; + zr_widget_text(out, content, X, 1, &text, ZR_TEXT_CENTERED, font); + } break; + case ZR_SYMBOL_CIRCLE: + case ZR_SYMBOL_CIRCLE_FILLED: + case ZR_SYMBOL_RECT: + case ZR_SYMBOL_RECT_FILLED: { + /* simple empty/filled shapes */ + if (sym->type == ZR_SYMBOL_RECT || sym->type == ZR_SYMBOL_RECT_FILLED) { + zr_draw_rect(out, content, 0, sym->foreground); + if (sym->type == ZR_SYMBOL_RECT_FILLED) + zr_draw_rect(out, zr_shrink_rect(content, + sym->border_width), 0, sym->background); + } else { + zr_draw_circle(out, content, sym->foreground); + if (sym->type == ZR_SYMBOL_CIRCLE_FILLED) + zr_draw_circle(out, zr_shrink_rect(content, 1), + sym->background); + } + } break; + case ZR_SYMBOL_TRIANGLE_UP: + case ZR_SYMBOL_TRIANGLE_DOWN: + case ZR_SYMBOL_TRIANGLE_LEFT: + case ZR_SYMBOL_TRIANGLE_RIGHT: { + enum zr_heading heading; + struct zr_vec2 points[3]; + heading = (sym->type == ZR_SYMBOL_TRIANGLE_RIGHT) ? ZR_RIGHT : + (sym->type == ZR_SYMBOL_TRIANGLE_LEFT) ? ZR_LEFT: + (sym->type == ZR_SYMBOL_TRIANGLE_UP) ? ZR_UP: ZR_DOWN; + zr_triangle_from_direction(points, content, 0, 0, heading); + zr_draw_triangle(out, points[0].x, points[0].y, + points[1].x, points[1].y, points[2].x, points[2].y, sym->foreground); + } break; + default: + case ZR_SYMBOL_MAX: break; + } +} + +static int +zr_button_behavior(enum zr_widget_status *state, struct zr_rect r, + const struct zr_input *i, enum zr_button_behavior behavior) +{ + int ret = 0; + *state = ZR_INACTIVE; + if (!i) return 0; + if (zr_input_is_mouse_hovering_rect(i, r)) { + *state = ZR_HOVERED; + if (zr_input_is_mouse_down(i, ZR_BUTTON_LEFT)) + *state = ZR_ACTIVE; + if (zr_input_has_mouse_click_in_rect(i, ZR_BUTTON_LEFT, r)) { + ret = (behavior != ZR_BUTTON_DEFAULT) ? + zr_input_is_mouse_down(i, ZR_BUTTON_LEFT): + zr_input_is_mouse_released(i, ZR_BUTTON_LEFT); + } + } + return ret; +} + +static void +zr_button_draw(struct zr_command_buffer *o, struct zr_rect r, + const struct zr_button *b, enum zr_widget_status state) +{ + struct zr_color background; + switch (state) { + default: + case ZR_INACTIVE: + background = b->normal; break; + case ZR_HOVERED: + background = b->hover; break; + case ZR_ACTIVE: + background = b->active; break; + } + zr_draw_rect(o, r, b->rounding, b->border); + zr_draw_rect(o, zr_shrink_rect(r, b->border_width), + b->rounding, background); +} + +static int +zr_do_button(enum zr_widget_status *state, + struct zr_command_buffer *o, struct zr_rect r, + const struct zr_button *b, const struct zr_input *i, + enum zr_button_behavior behavior, struct zr_rect *content) +{ + int ret = zr_false; + struct zr_vec2 pad; + struct zr_rect bounds; + ZR_ASSERT(b); + if (!o || !b) + return zr_false; + + /* calculate button content space */ + pad.x = b->padding.x + b->border_width; + pad.y = b->padding.y + b->border_width; + *content = zr_pad_rect(r, pad); + + /* execute and draw button */ + bounds.x = r.x - b->touch_pad.x; + bounds.y = r.y - b->touch_pad.y; + bounds.w = r.w + 2 * b->touch_pad.x; + bounds.h = r.h + 2 * b->touch_pad.y; + ret = zr_button_behavior(state, bounds, i, behavior); + zr_button_draw(o, r, b, *state); + return ret; +} + +static int +zr_do_button_text(enum zr_widget_status *state, + struct zr_command_buffer *o, struct zr_rect r, + const char *string, enum zr_button_behavior behavior, + const struct zr_button_text *b, const struct zr_input *i, + const struct zr_user_font *f) +{ + struct zr_text t; + struct zr_rect content; + int ret = zr_false; + + ZR_ASSERT(b); + ZR_ASSERT(o); + ZR_ASSERT(string); + ZR_ASSERT(f); + if (!o || !b || !f) + return zr_false; + + ret = zr_do_button(state, o, r, &b->base, i, behavior, &content); + switch (*state) { + default: + case ZR_INACTIVE: + t.background = b->base.normal; + t.text = b->normal; + break; + case ZR_HOVERED: + t.background = b->base.hover; + t.text = b->hover; + break; + case ZR_ACTIVE: + t.background = b->base.active; + t.text = b->active; + break; + } + t.padding = zr_vec2(0,0); + zr_widget_text(o, content, string, zr_strsiz(string), &t, b->alignment, f); + return ret; +} + +static int +zr_do_button_symbol(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect r, + enum zr_symbol_type symbol, enum zr_button_behavior bh, + const struct zr_button_symbol *b, const struct zr_input *in, + const struct zr_user_font *font) +{ + struct zr_symbol sym; + struct zr_color background; + struct zr_color color; + struct zr_rect content; + int ret; + + ZR_ASSERT(b); + ZR_ASSERT(out); + if (!out || !b) + return zr_false; + + ret = zr_do_button(state, out, r, &b->base, in, bh, &content); + switch (*state) { + default: + case ZR_INACTIVE: + background = b->base.normal; + color = b->normal; + break; + case ZR_HOVERED: + background = b->base.hover; + color = b->hover; + break; + case ZR_ACTIVE: + background = b->base.active; + color = b->active; + break; + } + + sym.type = symbol; + sym.background = background; + sym.foreground = color; + sym.border_width = b->base.border_width; + zr_draw_symbol(out, &sym, content, font); + return ret; +} + +static int +zr_do_button_image(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect r, + struct zr_image img, enum zr_button_behavior b, + const struct zr_button_icon *button, const struct zr_input *in) +{ + int pressed; + struct zr_rect bounds; + + ZR_ASSERT(button); + ZR_ASSERT(out); + if (!out || !button) + return zr_false; + + pressed = zr_do_button(state, out, r, &button->base, in, b, &bounds); + zr_draw_image(out, bounds, &img); + return pressed; +} + +static int +zr_do_button_text_symbol(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect r, + enum zr_symbol_type symbol, const char *text, zr_flags align, + enum zr_button_behavior behavior, const struct zr_button_text *b, + const struct zr_user_font *f, const struct zr_input *i) +{ + int ret; + struct zr_rect tri = {0,0,0,0}; + struct zr_color background, color; + struct zr_symbol sym; + + ZR_ASSERT(b); + ZR_ASSERT(out); + if (!out || !b) + return zr_false; + + ret = zr_do_button_text(state, out, r, text, behavior, b, i, f); + switch (*state) { + default: + case ZR_INACTIVE: + background = b->base.normal; + color = b->normal; + break; + case ZR_HOVERED: + background = b->base.hover; + color = b->hover; + break; + case ZR_ACTIVE: + background = b->base.active; + color = b->active; + break; + } + + /* calculate symbol bounds */ + tri.y = r.y + (r.h/2) - f->height/2; + tri.w = f->height; tri.h = f->height; + if (align == ZR_TEXT_LEFT) { + tri.x = (r.x + r.w) - (2 * b->base.padding.x + tri.w); + tri.x = MAX(tri.x, 0); + } else tri.x = r.x + 2 * b->base.padding.x; + + sym.type = symbol; + sym.background = background; + sym.foreground = color; + sym.border_width = 1.0f; + zr_draw_symbol(out, &sym, tri, f); + return ret; +} + +static int +zr_do_button_text_image(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect r, + struct zr_image img, const char* text, zr_flags align, + enum zr_button_behavior behavior, const struct zr_button_text *b, + const struct zr_user_font *f, const struct zr_input *i) +{ + int pressed; + struct zr_rect icon; + ZR_ASSERT(b); + ZR_ASSERT(out); + if (!out || !b) + return zr_false; + + pressed = zr_do_button_text(state, out, r, text, behavior, b, i, f); + icon.y = r.y + b->base.padding.y; + icon.w = icon.h = r.h - 2 * b->base.padding.y; + if (align == ZR_TEXT_LEFT) { + icon.x = (r.x + r.w) - (2 * b->base.padding.x + icon.w); + icon.x = MAX(icon.x, 0); + } else icon.x = r.x + 2 * b->base.padding.x; + zr_draw_image(out, icon, &img); + return pressed; +} + +/* =============================================================== + * + * TOGGLE + * + * ===============================================================*/ +enum zr_toggle_type { + ZR_TOGGLE_CHECK, + ZR_TOGGLE_OPTION +}; + +struct zr_toggle { + float rounding; + struct zr_vec2 touch_pad; + struct zr_vec2 padding; + struct zr_color font; + struct zr_color font_background; + struct zr_color background; + struct zr_color normal; + struct zr_color hover; + struct zr_color cursor; +}; + +static int +zr_toggle_behavior(const struct zr_input *in, struct zr_rect select, + enum zr_widget_status *state, int active) +{ + *state = ZR_INACTIVE; + if (in && zr_input_is_mouse_hovering_rect(in, select)) + *state = ZR_HOVERED; + if (zr_input_mouse_clicked(in, ZR_BUTTON_LEFT, select)) { + *state = ZR_ACTIVE; + active = !active; + } + return active; +} + +static void +zr_toggle_draw(struct zr_command_buffer *out, + enum zr_widget_status state, + const struct zr_toggle *toggle, int active, + enum zr_toggle_type type, struct zr_rect r, + const char *string, const struct zr_user_font *font) +{ + float cursor_pad; + struct zr_color col; + struct zr_rect select; + struct zr_rect cursor; + + select.w = MIN(r.h, font->height + toggle->padding.y); + select.h = select.w; + select.x = r.x + toggle->padding.x; + select.y = (r.y + toggle->padding.y + (select.w / 2)) - (font->height / 2); + cursor_pad = (type == ZR_TOGGLE_OPTION) ? + (float)(int)(select.w / 4): + (float)(int)(select.h / 6); + + /* calculate the bounds of the cursor inside the toggle */ + select.h = MAX(select.w, cursor_pad * 2); + cursor.h = select.h - cursor_pad * 2; + cursor.w = cursor.h; + cursor.x = select.x + cursor_pad; + cursor.y = select.y + cursor_pad; + + if (state == ZR_HOVERED || state == ZR_ACTIVE) + col = toggle->hover; + else col = toggle->normal; + + /* draw radiobutton/checkbox background */ + if (type == ZR_TOGGLE_CHECK) + zr_draw_rect(out, select , toggle->rounding, col); + else zr_draw_circle(out, select, col); + + /* draw radiobutton/checkbox cursor if active */ + if (active) { + if (type == ZR_TOGGLE_CHECK) + zr_draw_rect(out, cursor, toggle->rounding, toggle->cursor); + else zr_draw_circle(out, cursor, toggle->cursor); + } + + /* draw toggle text */ + if (string) { + struct zr_text text; + struct zr_rect inner; + + /* calculate text bounds */ + inner.x = r.x + select.w + toggle->padding.x * 2; + inner.y = select.y; + inner.w = MAX(r.x + r.w, inner.x + toggle->padding.x); + inner.w -= (inner.x + toggle->padding.x); + inner.h = select.w; + + /* draw text */ + text.padding.x = 0; + text.padding.y = 0; + text.background = toggle->font_background; + text.text = toggle->font; + zr_widget_text(out, inner, string, zr_strsiz(string), + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + } +} + +static void +zr_do_toggle(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect r, + int *active, const char *string, enum zr_toggle_type type, + const struct zr_toggle *toggle, const struct zr_input *in, + const struct zr_user_font *font) +{ + struct zr_rect bounds; + ZR_ASSERT(toggle); + ZR_ASSERT(out); + ZR_ASSERT(font); + if (!out || !toggle || !font || !active) + return; + + r.w = MAX(r.w, font->height + 2 * toggle->padding.x); + r.h = MAX(r.h, font->height + 2 * toggle->padding.y); + + bounds.x = r.x - toggle->touch_pad.x; + bounds.y = r.y - toggle->touch_pad.y; + bounds.w = r.w + 2 * toggle->touch_pad.x; + bounds.h = r.h + 2 * toggle->touch_pad.y; + + *active = zr_toggle_behavior(in, bounds, state, *active); + zr_toggle_draw(out, *state, toggle, *active, type, r, string, font); +} + +/* =============================================================== + * + * SLIDER + * + * ===============================================================*/ +struct zr_slider { + struct zr_vec2 padding; + struct zr_color border; + struct zr_color bg; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; + float rounding; +}; + +static float +zr_slider_behavior(enum zr_widget_status *state, struct zr_rect *cursor, + const struct zr_input *in, const struct zr_slider *s, struct zr_rect slider, + float slider_min, float slider_max, float slider_value, + float slider_step, float slider_steps) +{ + int inslider, incursor; + inslider = in && zr_input_is_mouse_hovering_rect(in, slider); + incursor = in && zr_input_has_mouse_click_down_in_rect(in, ZR_BUTTON_LEFT, + slider, zr_true); + + *state = (inslider) ? ZR_HOVERED: ZR_INACTIVE; + if (in && inslider && incursor) + { + const float d = in->mouse.pos.x - (cursor->x + cursor->w / 2.0f); + const float pxstep = (slider.w - (2 * s->padding.x)) / slider_steps; + + /* only update value if the next slider step is reached */ + *state = ZR_ACTIVE; + if (ZR_ABS(d) >= pxstep) { + float ratio = 0; + const float steps = (float)((int)(ZR_ABS(d) / pxstep)); + slider_value += (d > 0) ? (slider_step*steps) : -(slider_step*steps); + slider_value = CLAMP(slider_min, slider_value, slider_max); + ratio = (slider_value - slider_min)/slider_step; + cursor->x = slider.x + (cursor->w * ratio); + } + } + return slider_value; +} + +static void +zr_slider_draw(struct zr_command_buffer *out, + enum zr_widget_status state, const struct zr_slider *s, + struct zr_rect bar, struct zr_rect cursor, + float slider_min, float slider_max, float slider_value) +{ + struct zr_color col; + struct zr_rect fill; + + switch (state) { + default: + case ZR_INACTIVE: + col = s->normal; break; + case ZR_HOVERED: + col = s->hover; break; + case ZR_ACTIVE: + col = s->active; break; + } + + cursor.w = cursor.h; + cursor.x = (slider_value <= slider_min) ? cursor.x: + (slider_value >= slider_max) ? ((bar.x + bar.w) - cursor.w) : + cursor.x - (cursor.w/2); + + fill.x = bar.x; + fill.y = bar.y; + fill.w = (cursor.x + (cursor.w/2.0f)) - bar.x; + fill.h = bar.h; + + zr_draw_rect(out, bar, 0, s->bg); + zr_draw_rect(out, fill, 0, col); + zr_draw_circle(out, cursor, col); +} + +static float +zr_do_slider(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect slider, + float min, float val, float max, float step, + const struct zr_slider *s, const struct zr_input *in) +{ + float slider_range; + float slider_min, slider_max; + float slider_value, slider_steps; + float cursor_offset; + struct zr_rect cursor; + struct zr_rect bar; + + ZR_ASSERT(s); + ZR_ASSERT(out); + if (!out || !s) + return 0; + + /* make sure the provided values are correct */ + slider.x = slider.x + s->padding.x; + slider.y = slider.y + s->padding.y; + slider.h = MAX(slider.h, 2 * s->padding.y); + slider.w = MAX(slider.w, 1 + slider.h + 2 * s->padding.x); + slider.h -= 2 * s->padding.y; + slider.w -= 2 * s->padding.y; + + slider_max = MAX(min, max); + slider_min = MIN(min, max); + slider_value = CLAMP(slider_min, val, slider_max); + slider_range = slider_max - slider_min; + slider_steps = slider_range / step; + + /* calculate slider virtual cursor bounds */ + cursor_offset = (slider_value - slider_min) / step; + cursor.h = slider.h; + cursor.w = slider.w / (slider_steps + 1); + cursor.x = slider.x + (cursor.w * cursor_offset); + cursor.y = slider.y; + + /* calculate slider background bar bounds */ + bar.x = slider.x; + bar.y = (slider.y + cursor.h/2) - cursor.h/8; + bar.w = slider.w; + bar.h = slider.h/4; + + slider_value = zr_slider_behavior(state, &cursor, in, s, slider, + slider_min, slider_max, slider_value, step, slider_steps); + zr_slider_draw(out, *state, s, bar, cursor, slider_min, + slider_max, slider_value); + return slider_value; +} + +/* =============================================================== + * + * PROGRESSBAR + * + * ===============================================================*/ +struct zr_progress { + struct zr_vec2 padding; + struct zr_color border; + struct zr_color background; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; +}; + +static zr_size +zr_progress_behavior(enum zr_widget_status *state, const struct zr_input *in, + struct zr_rect r, zr_size max, zr_size value, int modifiable) +{ + *state = ZR_INACTIVE; + if (in && modifiable && zr_input_is_mouse_hovering_rect(in, r)) { + if (zr_input_is_mouse_down(in, ZR_BUTTON_LEFT)) { + float ratio = MAX(0, (float)(in->mouse.pos.x - r.x)) / (float)r.w; + value = (zr_size)MAX(0,((float)max * ratio)); + *state = ZR_ACTIVE; + } else *state = ZR_HOVERED; + } + if (!max) return value; + value = MIN(value, max); + return value; +} + +static void +zr_progress_draw(struct zr_command_buffer *out, const struct zr_progress *p, + enum zr_widget_status state, struct zr_rect r, zr_size max, zr_size value) +{ + struct zr_color col; + float prog_scale; + switch (state) { + default: + case ZR_INACTIVE: + col = p->normal; break; + case ZR_HOVERED: + col = p->hover; break; + case ZR_ACTIVE: + col = p->active; break; + } + + prog_scale = (float)value / (float)max; + zr_draw_rect(out, r, 0, p->background); + r.w = (r.w - 2) * prog_scale; + zr_draw_rect(out, r, 0, col); +} + +static zr_size +zr_do_progress(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect r, + zr_size value, zr_size max, int modifiable, + const struct zr_progress *prog, const struct zr_input *in) +{ + zr_size prog_value; + ZR_ASSERT(prog); + ZR_ASSERT(out); + if (!out || !prog) return 0; + + r.w = MAX(r.w, 2 * prog->padding.x); + r.h = MAX(r.h, 2 * prog->padding.y); + r = zr_pad_rect(r, zr_vec2(prog->padding.x, prog->padding.y)); + + prog_value = MIN(value, max); + prog_value = zr_progress_behavior(state, in, r, max, prog_value, modifiable); + zr_progress_draw(out, prog, *state, r, max, value); + return prog_value; +} + +/* =============================================================== + * + * SCROLLBAR + * + * ===============================================================*/ +struct zr_scrollbar { + float rounding; + struct zr_color border; + struct zr_color background; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; + int has_scrolling; +}; + +static float +zr_scrollbar_behavior(enum zr_widget_status *state, struct zr_input *in, + const struct zr_scrollbar *s, struct zr_rect scroll, + struct zr_rect cursor, float scroll_offset, + float target, float scroll_step, enum zr_orientation o) +{ + int left_mouse_down, left_mouse_click_in_cursor; + if (!in) return scroll_offset; + + *state = ZR_INACTIVE; + left_mouse_down = in->mouse.buttons[ZR_BUTTON_LEFT].down; + left_mouse_click_in_cursor = zr_input_has_mouse_click_down_in_rect(in, + ZR_BUTTON_LEFT, cursor, zr_true); + if (zr_input_is_mouse_hovering_rect(in, cursor)) + *state = ZR_HOVERED; + + if (left_mouse_down && left_mouse_click_in_cursor) { + /* update cursor by mouse dragging */ + float pixel, delta; + *state = ZR_ACTIVE; + if (o == ZR_VERTICAL) { + pixel = in->mouse.delta.y; + delta = (pixel / scroll.h) * target; + scroll_offset = CLAMP(0, scroll_offset + delta, target - scroll.h); + /* This is probably one of my most distgusting hacks I have ever done. + * This basically changes the mouse clicked position with the moving + * cursor. This allows for better scroll behavior but resulted into me + * having to remove const correctness for input. But in the end I believe + * it is worth it. */ + in->mouse.buttons[ZR_BUTTON_LEFT].clicked_pos.y += in->mouse.delta.y; + } else { + pixel = in->mouse.delta.x; + delta = (pixel / scroll.w) * target; + scroll_offset = CLAMP(0, scroll_offset + delta, target - scroll.w); + in->mouse.buttons[ZR_BUTTON_LEFT].clicked_pos.x += in->mouse.delta.x; + } + } else if (s->has_scrolling && ((in->mouse.scroll_delta<0) || + (in->mouse.scroll_delta>0))) { + /* update cursor by mouse scrolling */ + scroll_offset = scroll_offset + scroll_step * (-in->mouse.scroll_delta); + if (o == ZR_VERTICAL) + scroll_offset = CLAMP(0, scroll_offset, target - scroll.h); + else scroll_offset = CLAMP(0, scroll_offset, target - scroll.w); + } + return scroll_offset; +} + +static void +zr_scrollbar_draw(struct zr_command_buffer *out, const struct zr_scrollbar *s, + enum zr_widget_status state, struct zr_rect scroll, struct zr_rect cursor) +{ + struct zr_color col; + switch (state) { + default: + case ZR_INACTIVE: + col = s->normal; break; + case ZR_HOVERED: + col = s->hover; break; + case ZR_ACTIVE: + col = s->active; break; + } + zr_draw_rect(out, zr_shrink_rect(scroll,1), s->rounding, s->border); + zr_draw_rect(out, scroll, s->rounding, s->background); + zr_draw_rect(out, cursor, s->rounding, col); +} + +static float +zr_do_scrollbarv(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect scroll, + float offset, float target, float step, const struct zr_scrollbar *s, + struct zr_input *i) +{ + struct zr_rect cursor; + float scroll_step; + float scroll_offset; + float scroll_off, scroll_ratio; + + ZR_ASSERT(out); + ZR_ASSERT(s); + ZR_ASSERT(state); + if (!out || !s) return 0; + + /* scrollbar background */ + scroll.w = MAX(scroll.w, 1); + scroll.h = MAX(scroll.h, 2 * scroll.w); + if (target <= scroll.h) return 0; + + /* calculate scrollbar constants */ + scroll_step = MIN(step, scroll.h); + scroll_offset = MIN(offset, target - scroll.h); + scroll_ratio = scroll.h / target; + scroll_off = scroll_offset / target; + + /* calculate scrollbar cursor bounds */ + cursor.h = (scroll_ratio * scroll.h - 2); + cursor.y = scroll.y + (scroll_off * scroll.h) + 1; + cursor.w = scroll.w - 2; + cursor.x = scroll.x + 1; + + /* draw scrollbar */ + scroll_offset = zr_scrollbar_behavior(state, i, s, scroll, cursor, + scroll_offset, target, scroll_step, ZR_VERTICAL); + scroll_off = scroll_offset / target; + cursor.y = scroll.y + (scroll_off * scroll.h); + zr_scrollbar_draw(out, s, *state, scroll, cursor); + return scroll_offset; +} + +static float +zr_do_scrollbarh(enum zr_widget_status *state, + struct zr_command_buffer *out, struct zr_rect scroll, + float offset, float target, float step, const struct zr_scrollbar *s, + struct zr_input *i) +{ + struct zr_rect cursor; + float scroll_step; + float scroll_offset; + float scroll_off, scroll_ratio; + + ZR_ASSERT(out); + ZR_ASSERT(s); + if (!out || !s) return 0; + + /* scrollbar background */ + scroll.h = MAX(scroll.h, 1); + scroll.w = MAX(scroll.w, 2 * scroll.h); + if (target <= scroll.w) return 0; + + /* calculate scrollbar constants */ + scroll_step = MIN(step, scroll.w); + scroll_offset = MIN(offset, target - scroll.w); + scroll_ratio = scroll.w / target; + scroll_off = scroll_offset / target; + + /* calculate scrollbar cursor bounds */ + cursor.w = scroll_ratio * scroll.w - 2; + cursor.x = scroll.x + (scroll_off * scroll.w) + 1; + cursor.h = scroll.h - 2; + cursor.y = scroll.y + 1; + + /* draw scrollbar */ + scroll_offset = zr_scrollbar_behavior(state, i, s, scroll, cursor, + scroll_offset, target, scroll_step, ZR_HORIZONTAL); + scroll_off = scroll_offset / target; + cursor.x = scroll.x + (scroll_off * scroll.w); + zr_scrollbar_draw(out, s, *state, scroll, cursor); + return scroll_offset; +} + +/* =============================================================== + * + * EDIT + * + * ===============================================================*/ +struct zr_edit { + int modifiable; + float border_size; + float rounding; + float scrollbar_width; + struct zr_vec2 padding; + int show_cursor; + struct zr_color background; + struct zr_color border; + struct zr_color cursor; + struct zr_color text; + struct zr_scrollbar scroll; +}; + +static void +zr_edit_box_handle_input(struct zr_edit_box *box, const struct zr_input *in, + int has_special) +{ + char *buffer = zr_edit_box_get(box); + zr_size len = zr_edit_box_len_char(box); + zr_size min = MIN(box->sel.end, box->sel.begin); + zr_size maxi = MAX(box->sel.end, box->sel.begin); + zr_size diff = maxi - min; + int enter, tab; + + /* text manipulation */ + if (zr_input_is_key_pressed(in,ZR_KEY_DEL)) + zr_edit_box_remove(box, ZR_DELETE); + else if (zr_input_is_key_pressed(in,ZR_KEY_BACKSPACE)) + zr_edit_box_remove(box, ZR_REMOVE); + + enter = has_special && zr_input_is_key_pressed(in, ZR_KEY_ENTER); + tab = has_special && zr_input_is_key_pressed(in, ZR_KEY_TAB); + if (in->keyboard.text_len || enter || tab) { + if (diff && box->cursor != box->glyphs) { + /* replace text selection */ + zr_edit_box_remove(box, ZR_DELETE); + box->cursor = min; + } + if (enter) zr_edit_box_add(box, "\n", 1); + else if (tab) zr_edit_box_add(box, " ", 4); + else zr_edit_box_buffer_input(box, in); + box->sel.begin = box->cursor; + box->sel.end = box->cursor; + } + + /* cursor key movement */ + if (zr_input_is_key_pressed(in, ZR_KEY_LEFT)) { + box->cursor = (zr_size)(MAX(0, (int)box->cursor - 1)); + box->sel.begin = box->cursor; + box->sel.end = box->cursor; + } + if (zr_input_is_key_pressed(in, ZR_KEY_RIGHT) && box->cursor < box->glyphs) { + box->cursor = MIN((!box->glyphs) ? 0 : box->glyphs, box->cursor + 1); + box->sel.begin = box->cursor; + box->sel.end = box->cursor; + } + + /* copy & cut & paste functionlity */ + if (zr_input_is_key_pressed(in, ZR_KEY_PASTE) && box->clip.paste) + box->clip.paste(box->clip.userdata, box); + if ((zr_input_is_key_pressed(in, ZR_KEY_COPY) && box->clip.copy) || + (zr_input_is_key_pressed(in, ZR_KEY_CUT) && box->clip.copy)) { + if (diff && box->cursor != box->glyphs) { + /* copy or cut text selection */ + zr_size l; + zr_rune unicode; + char *begin, *end; + begin = zr_edit_buffer_at(&box->buffer, (int)min, &unicode, &l); + end = zr_edit_buffer_at(&box->buffer, (int)maxi, &unicode, &l); + box->clip.copy(box->clip.userdata, begin, (zr_size)(end - begin)); + if (zr_input_is_key_pressed(in, ZR_KEY_CUT)) + zr_edit_box_remove(box, ZR_DELETE); + } else { + /* copy or cut complete buffer */ + box->clip.copy(box->clip.userdata, buffer, len); + if (zr_input_is_key_pressed(in, ZR_KEY_CUT)) + zr_edit_box_clear(box); + } + } +} + +static void +zr_widget_edit_field(struct zr_command_buffer *out, struct zr_rect r, + struct zr_edit_box *box, const struct zr_edit *field, + const struct zr_input *in, const struct zr_user_font *font) +{ + char *buffer; + zr_size len; + struct zr_text text; + + ZR_ASSERT(out); + ZR_ASSERT(font); + ZR_ASSERT(field); + if (!out || !box || !field) + return; + + r.w = MAX(r.w, 2 * field->padding.x + 2 * field->border_size); + r.h = MAX(r.h, font->height + (2 * field->padding.y + 2 * field->border_size)); + + /* draw editbox background and border */ + zr_draw_rect(out, r, field->rounding, field->border); + zr_draw_rect(out, zr_shrink_rect(r, field->border_size), + field->rounding, field->background); + + /* check if the editbox is activated/deactivated */ + if (in && in->mouse.buttons[ZR_BUTTON_LEFT].clicked && + in->mouse.buttons[ZR_BUTTON_LEFT].down) + box->active = ZR_INBOX(in->mouse.pos.x,in->mouse.pos.y,r.x,r.y,r.w,r.h); + + /* input handling */ + if (box->active && in) + zr_edit_box_handle_input(box, in, 0); + + buffer = zr_edit_box_get(box); + len = zr_edit_box_len_char(box); + { + /* text management */ + struct zr_rect label; + zr_size cursor_w = font->width(font->userdata,font->height,"X", 1); + zr_size text_len = len; + zr_size glyph_off = 0; + zr_size glyph_cnt = 0; + zr_size offset = 0; + float text_width = 0; + + /* calculate text frame */ + label.w = MAX(r.w, - 2 * field->padding.x - 2 * field->border_size); + label.w -= 2 * field->padding.x - 2 * field->border_size; + { + zr_size frames = 0; + zr_size glyphs = 0; + zr_size frame_len = 0; + zr_size row_len = 0; + float space = MAX(label.w, (float)cursor_w); + space -= (float)cursor_w; + + while (text_len) { + frames++; + offset += frame_len; + frame_len = zr_user_font_glyphs_fitting_in_space(font, + &buffer[offset], text_len, space, &row_len, &glyphs, &text_width, 0); + glyph_off += glyphs; + if (glyph_off > box->cursor || !frame_len) break; + text_len -= frame_len; + } + + text_len = frame_len; + glyph_cnt = glyphs; + glyph_off = (frames <= 1) ? 0 : (glyph_off - glyphs); + offset = (frames <= 1) ? 0 : offset; + } + + /* set cursor by mouse click and handle text selection */ + if (in && field->show_cursor && in->mouse.buttons[ZR_BUTTON_LEFT].down && box->active) { + const char *visible = &buffer[offset]; + float xoff = in->mouse.pos.x - (r.x + field->padding.x + field->border_size); + if (ZR_INBOX(in->mouse.pos.x, in->mouse.pos.y, r.x, r.y, r.w, r.h)) + { + /* text selection in the current text frame */ + zr_size glyph_index; + zr_size glyph_pos=zr_user_font_glyph_index_at_pos(font,visible,text_len,xoff); + if (glyph_cnt + glyph_off >= box->glyphs) + glyph_index = glyph_off + MIN(glyph_pos, glyph_cnt); + else glyph_index = glyph_off + MIN(glyph_pos, glyph_cnt-1); + + if (text_len) + zr_edit_box_set_cursor(box, glyph_index); + if (!box->sel.active) { + box->sel.active = zr_true; + box->sel.begin = glyph_index; + box->sel.end = box->sel.begin; + } else { + if (box->sel.begin > glyph_index) { + box->sel.end = glyph_index; + box->sel.active = zr_true; + } + } + } else if (!ZR_INBOX(in->mouse.pos.x,in->mouse.pos.y,r.x,r.y,r.w,r.h) && + ZR_INBOX(in->mouse.buttons[ZR_BUTTON_LEFT].clicked_pos.x, + in->mouse.buttons[ZR_BUTTON_LEFT].clicked_pos.y,r.x,r.y,r.w,r.h) + && box->cursor != box->glyphs && box->cursor > 0) + { + /* text selection out of the current text frame */ + zr_size glyph = ((in->mouse.pos.x > r.x) && + box->cursor+1 < box->glyphs) ? + box->cursor+1: box->cursor-1; + zr_edit_box_set_cursor(box, glyph); + if (box->sel.active) { + box->sel.end = glyph; + box->sel.active = zr_true; + } + } else box->sel.active = zr_false; + } else box->sel.active = zr_false; + + /* calculate the text bounds */ + label.x = r.x + field->padding.x + field->border_size; + label.y = r.y + field->padding.y + field->border_size; + label.h = r.h - (2 * field->padding.y + 2 * field->border_size); + + text.padding = zr_vec2(0,0); + text.background = field->background; + text.text = field->text; + zr_widget_text(out, label, &buffer[offset], text_len, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + /* draw selected text */ + if (box->active && field->show_cursor) { + if (box->cursor == box->glyphs) { + /* draw the cursor at the end of the string */ + text_width = font->width(font->userdata, font->height, + buffer + offset, text_len); + zr_draw_rect(out, zr_rect(label.x+(float)text_width, + label.y, (float)cursor_w, label.h), 0, field->cursor); + } else { + /* draw text selection */ + zr_size l = 0, s; + zr_rune unicode; + char *begin, *end; + zr_size off_begin, off_end; + zr_size min = MIN(box->sel.end, box->sel.begin); + zr_size maxi = MAX(box->sel.end, box->sel.begin); + struct zr_rect clip = out->clip; + + /* calculate selection text range */ + begin = zr_edit_buffer_at(&box->buffer, (int)min, &unicode, &l); + end = zr_edit_buffer_at(&box->buffer, (int)maxi, &unicode, &l); + off_begin = (zr_size)(begin - (char*)box->buffer.memory.ptr); + off_end = (zr_size)(end - (char*)box->buffer.memory.ptr); + + /* calculate selected text width */ + zr_draw_scissor(out, label); + s = font->width(font->userdata, font->height, buffer + offset, off_begin - offset); + label.x += (float)s; + s = font->width(font->userdata, font->height, begin, MAX(l, off_end - off_begin)); + label.w = (float)s; + + /* draw selected text */ + zr_draw_rect(out, label, 0, field->text); + text.padding = zr_vec2(0,0); + text.background = field->text; + text.text = field->background; + zr_widget_text(out, label, begin, MAX(l, off_end - off_begin), + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + zr_draw_scissor(out, clip); + } + } + } +} + +static void +zr_widget_edit_box(struct zr_command_buffer *out, struct zr_rect r, + struct zr_edit_box *box, const struct zr_edit *field, + struct zr_input *in, const struct zr_user_font *font) +{ + char *buffer; + zr_size len; + zr_size visible_rows = 0; + zr_size total_rows = 0; + zr_size cursor_w; + int prev_state; + + float total_width = 0; + float total_height = 0; + zr_size row_height = 0; + + ZR_ASSERT(out); + ZR_ASSERT(font); + ZR_ASSERT(field); + if (!out || !box || !field) + return; + + /* calculate usable field space */ + r.w = MAX(r.w, 2 * field->padding.x + 2 * field->border_size); + r.h = MAX(r.h, font->height + (2 * field->padding.y + 2 * field->border_size)); + + total_width = r.w - (2 * field->padding.x + 2 * field->border_size); + total_width -= field->scrollbar_width; + row_height = (zr_size)(font->height + field->padding.y); + + /* draw edit field background and border */ + zr_draw_rect(out, r, field->rounding, field->border); + zr_draw_rect(out, zr_shrink_rect(r, field->border_size), + field->rounding, field->background); + + /* check if edit box is big enough to show even a single row */ + visible_rows = (zr_size)(r.h - (2 * field->border_size + 2 * field->padding.y)); + visible_rows = (zr_size)((float)visible_rows / (font->height + field->padding.y)); + if (!visible_rows) return; + + /* check if editbox is activated/deactivated */ + prev_state = box->active; + if (in && in->mouse.buttons[ZR_BUTTON_LEFT].clicked && + in->mouse.buttons[ZR_BUTTON_LEFT].down) + box->active = ZR_INBOX(in->mouse.pos.x,in->mouse.pos.y,r.x,r.y,r.w,r.h); + + /* text input handling */ + if (box->active && in && field->modifiable) + zr_edit_box_handle_input(box, in, 1); + + buffer = zr_edit_box_get(box); + len = zr_edit_box_len_char(box); + cursor_w = font->width(font->userdata,font->height,(const char*)"X", 1); + { + /* calulate total number of needed rows */ + zr_size glyphs = 0; + zr_size row_off = 0; + zr_size text_len = len; + zr_size offset = 0; + zr_size row_len; + + float space = total_width; + float text_width = 0; + + while (text_len) { + total_rows++; + offset += row_off; + row_off = zr_user_font_glyphs_fitting_in_space(font, + &buffer[offset], text_len, space, &row_len, &glyphs, &text_width, 1); + if (!row_off){ + text_len = 0; + } else text_len -= row_off; + } + total_height = (float)total_rows * (float)row_height; + } + + if (!box->active || (!prev_state && box->active)) { + /* make sure edit box points to the end of the buffer if not active */ + if (total_rows > visible_rows) + box->scrollbar = (float)((total_rows - visible_rows) * row_height); + if (!prev_state && box->active) { + box->cursor = zr_utf_len(buffer, len); + box->sel.begin = box->cursor; + box->sel.end = box->cursor; + } + } + + if ((in && in->keyboard.text_len && total_rows >= visible_rows && box->active) || + box->sel.active || (box->text_inserted && total_rows >= visible_rows)) + { + /* make sure cursor is always in current visible field while writing */ + box->text_inserted = 0; + if (box->cursor == box->glyphs && !box->sel.active) { + /* cursor is at end of text and out of visible frame */ + float row_offset = (float)(total_rows - visible_rows); + box->scrollbar = (font->height + field->padding.x) * row_offset; + } else { + /* cursor is inside text and out of visible frame */ + float text_width; + zr_size cur_row = 0; + zr_size glyphs = 0; + zr_size row_off = 0; + zr_size row_len = 0; + zr_size text_len = len; + zr_size offset = 0, glyph_off = 0; + zr_size cursor = MIN(box->sel.end, box->sel.begin); + zr_size scroll_offset = (zr_size)(box->scrollbar / (float)row_height); + + /* find cursor row */ + while (text_len) { + offset += row_off; + row_off = zr_user_font_glyphs_fitting_in_space(font, + &buffer[offset], text_len, total_width, &row_len, &glyphs, &text_width, 1); + if ((cursor >= glyph_off && cursor < glyph_off + glyphs) || !row_off) + break; + + glyph_off += glyphs; + text_len -= row_off; + cur_row++; + } + + if (cur_row >= visible_rows && !box->sel.active) { + /* set visible frame to include cursor while writing */ + zr_size row_offset = (cur_row + 1) - visible_rows; + box->scrollbar = (font->height + field->padding.x) * (float)row_offset; + } else if (box->sel.active && scroll_offset > cur_row) { + /* set visible frame to include cursor while selecting */ + zr_size row_offset = (scroll_offset > 0) ? scroll_offset-1: scroll_offset; + box->scrollbar = (font->height + field->padding.x) * (float)row_offset; + } + } + } + if (box->text_inserted) { + /* @NOTE: zr_editbox_add handler: ugly but works */ + box->sel.begin = box->cursor; + box->sel.end = box->cursor; + box->text_inserted = 0; + } + + if (in && field->show_cursor && in->mouse.buttons[ZR_BUTTON_LEFT].down && box->active) + { + /* TEXT SELECTION */ + const char *visible = buffer; + float xoff = in->mouse.pos.x - (r.x + field->padding.x + field->border_size); + float yoff = in->mouse.pos.y - (r.y + field->padding.y + field->border_size); + + int in_space = (xoff >= 0 && xoff < total_width); + int in_region = (box->sel.active && yoff < 0) || + (yoff >= 0 && yoff < total_height); + + if (ZR_INBOX(in->mouse.pos.x, in->mouse.pos.y, r.x, r.y, r.w, r.h) && + in_space && in_region) + { + zr_size row; + zr_size glyph_index = 0, glyph_pos = 0; + zr_size cur_row = 0; + zr_size glyphs = 0; + zr_size row_off = box->glyphs; + zr_size row_len = 0; + zr_size text_len = len; + zr_size offset = 0, glyph_off = 0; + float text_width = 0; + + /* selection beyond the current visible text rows */ + if (yoff < 0 && box->sel.active) { + int off = ((int)yoff + (int)box->scrollbar - (int)row_height); + int next_row = off / (int)row_height; + row = (next_row < 0) ? 0 : (zr_size)next_row; + } else row = (zr_size)((yoff + box->scrollbar)/ + (font->height + field->padding.y)); + + /* find selected row */ + if (text_len) { + while (text_len && cur_row <= row) { + row_off = zr_user_font_glyphs_fitting_in_space(font, + &buffer[offset], text_len, total_width, &row_len, + &glyphs, &text_width, 1); + if (!row_off) break; + + glyph_off += glyphs; + text_len -= row_off; + visible += row_off; + offset += row_off; + cur_row++; + } + glyph_off -= glyphs; + visible -= row_off; + } + + /* find selected glyphs in row */ + if ((text_width + r.x + field->padding.y + field->border_size) > xoff) { + glyph_pos = zr_user_font_glyph_index_at_pos(font, visible, row_len, xoff); + if (glyph_pos + glyph_off >= box->glyphs) + glyph_index = box->glyphs; + else glyph_index = glyph_off + MIN(glyph_pos, glyphs-1); + + zr_edit_box_set_cursor(box, glyph_index); + if (!box->sel.active) { + box->sel.active = zr_true; + box->sel.begin = glyph_index; + box->sel.end = glyph_index; + } else { + if (box->sel.begin > glyph_index) { + box->sel.end = glyph_index; + box->sel.active = zr_true; + } + } + } + } else box->sel.active = zr_false; + } else box->sel.active = zr_false; + + { + /* SCROLLBAR */ + struct zr_rect bounds; + float scroll_target, scroll_offset, scroll_step; + struct zr_scrollbar scroll = field->scroll; + enum zr_widget_status state; + + bounds.x = (r.x + r.w) - (field->scrollbar_width + field->border_size); + bounds.y = r.y + field->border_size + field->padding.y; + bounds.w = field->scrollbar_width; + bounds.h = r.h - (2 * field->border_size + 2 * field->padding.y); + + scroll_offset = box->scrollbar; + scroll_step = total_height * 0.10f; + scroll_target = total_height; + scroll.has_scrolling = box->active; + box->scrollbar = zr_do_scrollbarv(&state, out, bounds, scroll_offset, + scroll_target, scroll_step, &scroll, in); + } + { + /* DRAW TEXT */ + zr_size text_len = len; + zr_size offset = 0; + zr_size row_off = 0; + zr_size row_len = 0; + zr_size glyphs = 0; + zr_size glyph_off = 0; + float text_width = 0; + struct zr_rect scissor; + struct zr_rect clip; + + struct zr_rect label; + struct zr_rect old_clip = out->clip; + + /* calculate clipping rect for scrollbar */ + clip = zr_shrink_rect(r, field->border_size); + clip.x += field->padding.x; + clip.y += field->padding.y; + clip.w -= 2 * field->padding.x; + clip.h -= 2 * field->padding.y; + zr_unify(&scissor, &out->clip, clip.x, clip.y, clip.x + clip.w, clip.y + clip.h); + + /* calculate row text space */ + zr_draw_scissor(out, scissor); + label.x = r.x + field->padding.x + field->border_size; + label.y = (r.y + field->padding.y + field->border_size) - box->scrollbar; + label.h = font->height + field->padding.y; + + /* draw each text row */ + while (text_len) { + /* selection bounds */ + struct zr_text text; + zr_size begin = MIN(box->sel.end, box->sel.begin); + zr_size end = MAX(box->sel.end, box->sel.begin); + + offset += row_off; + row_off = zr_user_font_glyphs_fitting_in_space(font, + &buffer[offset], text_len, total_width, &row_len, + &glyphs, &text_width, 1); + label.w = text_width; + if (!row_off || !row_len) break; + + /* draw either unselected or selected row */ + if (glyph_off <= begin && glyph_off + glyphs > begin && + glyph_off + glyphs <= end && box->active) + { + /* 1.) first case with selection beginning in current row */ + zr_size l = 0, sel_begin, sel_len; + zr_size unselected_text_width; + zr_rune unicode; + + /* calculate selection beginning string position */ + const char *from; + from = zr_utf_at(&buffer[offset], row_len, + (int)(begin - glyph_off), &unicode, &l); + sel_begin = (zr_size)(from - (char*)box->buffer.memory.ptr); + sel_begin = sel_begin - offset; + sel_len = row_len - sel_begin; + + /* draw unselected text part */ + unselected_text_width = + font->width(font->userdata, font->height, &buffer[offset], + (row_len >= sel_len) ? row_len - sel_len: 0); + + text.padding = zr_vec2(0,0); + text.background = field->background; + text.text = field->text; + zr_widget_text(out, label, &buffer[offset], + (row_len >= sel_len) ? row_len - sel_len: 0, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + /* draw selected text part */ + label.x += (float)(unselected_text_width); + label.w -= (float)(unselected_text_width); + text.background = field->text; + text.text = field->background; + zr_draw_rect(out, label, 0, field->text); + zr_widget_text(out, label, &buffer[offset+sel_begin], + sel_len, &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + label.x -= (float)unselected_text_width; + label.w += (float)(unselected_text_width); + } else if (glyph_off > begin && glyph_off + glyphs < end && box->active) { + /* 2.) selection spanning over current row */ + text.padding = zr_vec2(0,0); + text.background = field->text; + text.text = field->background; + zr_draw_rect(out, label, 0, field->text); + zr_widget_text(out, label, &buffer[offset], row_len, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + } else if (glyph_off > begin && glyph_off + glyphs >= end && + box->active && end >= glyph_off && end <= glyph_off + glyphs) { + /* 3.) selection ending in current row */ + zr_size l = 0, sel_end, sel_len; + zr_size selected_text_width; + zr_rune unicode; + + /* calculate selection beginning string position */ + const char *to = zr_utf_at(&buffer[offset], row_len, + (int)(end - glyph_off), &unicode, &l); + sel_end = (zr_size)(to - (char*)box->buffer.memory.ptr); + sel_len = (sel_end - offset); + sel_end = sel_end - offset; + + /* draw selected text part */ + selected_text_width = font->width(font->userdata, font->height, + &buffer[offset], sel_len); + text.padding = zr_vec2(0,0); + text.background = field->text; + text.text = field->background; + zr_draw_rect(out, label, 0, field->text); + zr_widget_text(out, label, &buffer[offset], sel_len, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + /* draw unselected text part */ + label.x += (float)selected_text_width; + label.w -= (float)(selected_text_width); + text.background = field->background; + text.text = field->text; + zr_widget_text(out, label, &buffer[offset+sel_end], + (row_len >= sel_len) ? row_len - sel_len: 0, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + label.x -= (float)selected_text_width; + label.w += (float)(selected_text_width); + } + else if (glyph_off <= begin && glyph_off + glyphs >= begin && + box->active && glyph_off <= end && glyph_off + glyphs > end) + { + /* 4.) selection beginning and ending in current row */ + zr_size l = 0; + zr_size cur_text_width; + zr_size sel_begin, sel_end, sel_len; + zr_rune unicode; + float label_x = label.x; + float label_w = label.w; + float tmp; + + const char *from = zr_utf_at(&buffer[offset], row_len, + (int)(begin - glyph_off), &unicode, &l); + const char *to = zr_utf_at(&buffer[offset], row_len, + (int)(end - glyph_off), &unicode, &l); + + /* calculate selection bounds and length */ + sel_begin = (zr_size)(from - (char*)box->buffer.memory.ptr); + sel_begin = sel_begin - offset; + sel_end = (zr_size)(to - (char*)box->buffer.memory.ptr); + sel_end = sel_end - offset; + sel_len = (sel_end - sel_begin); + if (!sel_len) { + sel_len = zr_utf_decode(&buffer[offset+sel_begin], + &unicode, row_len); + sel_end += zr_utf_decode(&buffer[offset+sel_end], + &unicode, row_len); + } + + /* draw beginning unselected text part */ + cur_text_width = font->width(font->userdata, font->height, + &buffer[offset], sel_begin); + text.padding = zr_vec2(0,0); + text.background = field->background; + text.text = field->text; + zr_widget_text(out, label, &buffer[offset], sel_begin, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + /* draw selected text part */ + label.x += (float)cur_text_width; + label.w -= (float)(cur_text_width); + tmp = label.w; + + text.background = field->text; + text.text = field->background; + label.w = font->width(font->userdata, font->height, + &buffer[offset+sel_begin], sel_len); + zr_draw_rect(out, label, 0, field->text); + zr_widget_text(out, label, &buffer[offset+sel_begin], sel_len, + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + cur_text_width = font->width(font->userdata, font->height, + &buffer[offset+sel_begin], sel_len); + label.w = tmp; + + /* draw ending unselected text part */ + label.x += (float)cur_text_width; + label.w -= (float)(cur_text_width); + text.background = field->background; + text.text = field->text; + zr_widget_text(out, label, &buffer[offset+sel_end], + row_len - (sel_len + sel_begin), + &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + + label.x = (float)label_x; + label.w = (float)label_w; + } else { + /* 5.) no selection */ + label.w = text_width; + text.background = field->background; + text.text = field->text; + zr_widget_text(out, label, &buffer[offset], + row_len, &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, font); + } + + glyph_off += glyphs; + text_len -= row_off; + label.y += font->height + field->padding.y; + } + + /* draw the cursor at the end of the string */ + if (box->active && field->show_cursor) { + if (box->cursor == box->glyphs) { + if (len) label.y -= (font->height + field->padding.y); + zr_draw_rect(out, zr_rect(label.x+(float)text_width, + label.y, (float)cursor_w, label.h), 0, field->cursor); + } + } + zr_draw_scissor(out, old_clip); + } +} + +int zr_filter_default(const struct zr_edit_box *box, zr_rune unicode) +{(void)unicode;ZR_UNUSED(box);return zr_true;} + +int +zr_filter_ascii(const struct zr_edit_box *box, zr_rune unicode) +{ + ZR_UNUSED(box); + if (unicode > 128) return zr_false; + else return zr_true; +} + +int +zr_filter_float(const struct zr_edit_box *box, zr_rune unicode) +{ + ZR_UNUSED(box); + if ((unicode < '0' || unicode > '9') && unicode != '.' && unicode != '-') + return zr_false; + else return zr_true; +} + +int +zr_filter_decimal(const struct zr_edit_box *box, zr_rune unicode) +{ + ZR_UNUSED(box); + if ((unicode < '0' || unicode > '9') && unicode != '-') + return zr_false; + else return zr_true; +} + +int +zr_filter_hex(const struct zr_edit_box *box, zr_rune unicode) +{ + ZR_UNUSED(box); + if ((unicode < '0' || unicode > '9') && + (unicode < 'a' || unicode > 'f') && + (unicode < 'A' || unicode > 'F')) + return zr_false; + else return zr_true; +} + +int +zr_filter_oct(const struct zr_edit_box *box, zr_rune unicode) +{ + ZR_UNUSED(box); + if (unicode < '0' || unicode > '7') + return zr_false; + else return zr_true; +} + +int +zr_filter_binary(const struct zr_edit_box *box, zr_rune unicode) +{ + ZR_UNUSED(box); + if (unicode != '0' && unicode != '1') + return zr_false; + else return zr_true; +} + +static zr_size +zr_widget_edit(struct zr_command_buffer *out, struct zr_rect r, + char *buffer, zr_size len, zr_size max, int *active, + zr_size *cursor, const struct zr_edit *field, zr_filter filter, + const struct zr_input *in, const struct zr_user_font *font) +{ + struct zr_edit_box box; + zr_edit_box_init(&box, buffer, max, 0, filter); + + box.buffer.allocated = len; + box.active = *active; + box.glyphs = zr_utf_len(buffer, len); + if (!cursor) { + box.cursor = box.glyphs; + } else{ + box.cursor = MIN(*cursor, box.glyphs); + box.sel.begin = box.cursor; + box.sel.end = box.cursor; + } + + zr_widget_edit_field(out, r, &box, field, in, font); + *active = box.active; + if (cursor) + *cursor = box.cursor; + return zr_edit_box_len_char(&box); +} +/* =============================================================== + * + * PROPERTY + * + * ===============================================================*/ +enum zr_property_state { + ZR_PROPERTY_DEFAULT, + ZR_PROPERTY_EDIT, + ZR_PROPERTY_DRAG +}; + +struct zr_property { + float border_size; + struct zr_vec2 padding; + struct zr_color border; + struct zr_color normal; + struct zr_color hover; + struct zr_color active; + struct zr_color text; + float rounding; +}; + +static float +zr_drag_behavior(enum zr_widget_status *state, const struct zr_input *in, + struct zr_rect drag, float min, float val, float max, float inc_per_pixel) +{ + int left_mouse_down = in && in->mouse.buttons[ZR_BUTTON_LEFT].down; + int left_mouse_click_in_cursor = in && + zr_input_has_mouse_click_down_in_rect(in, ZR_BUTTON_LEFT, drag, zr_true); + + *state = ZR_INACTIVE; + if (zr_input_is_mouse_hovering_rect(in, drag)) + *state = ZR_HOVERED; + + if (left_mouse_down && left_mouse_click_in_cursor) { + float delta, pixels; + pixels = in->mouse.delta.x; + delta = pixels * inc_per_pixel; + val += delta; + val = CLAMP(min, val, max); + *state = ZR_ACTIVE; + } + return val; +} + +static float +zr_property_behavior(enum zr_widget_status *ws, const struct zr_input *in, + struct zr_rect property, struct zr_rect left, struct zr_rect right, + struct zr_rect label, struct zr_rect edit, struct zr_rect empty, + int *state, float min, float value, float max, float step, float inc_per_pixel) +{ + if (zr_button_behavior(ws, left, in, ZR_BUTTON_DEFAULT)) + value = CLAMP(min, value - step, max); + if (zr_button_behavior(ws, right, in, ZR_BUTTON_DEFAULT)) + value = CLAMP(min, value + step, max); + + if (*state == ZR_PROPERTY_DEFAULT) { + if (zr_button_behavior(ws, edit, in, ZR_BUTTON_DEFAULT)) + *state = ZR_PROPERTY_EDIT; + else if (zr_input_has_mouse_click_in_rect(in, ZR_BUTTON_LEFT, label)) + *state = ZR_PROPERTY_DRAG; + else if (zr_input_has_mouse_click_in_rect(in, ZR_BUTTON_LEFT, empty)) + *state = ZR_PROPERTY_DRAG; + } + if (*state == ZR_PROPERTY_DRAG) { + value = zr_drag_behavior(ws, in, property, min, value, max, inc_per_pixel); + if (*ws != ZR_ACTIVE) *state = ZR_PROPERTY_DEFAULT; + } + return value; +} + +static void +zr_property_draw(struct zr_command_buffer *out, + struct zr_property *p, struct zr_rect property, + struct zr_rect left, struct zr_rect right, + struct zr_rect label, const char *name, zr_size len, + const struct zr_user_font *f) +{ + struct zr_symbol sym; + struct zr_text text; + /* background */ + zr_draw_rect(out, property, p->rounding, p->border); + zr_draw_rect(out, zr_shrink_rect(property, p->border_size), + p->rounding, p->normal); + + /* buttons */ + sym.type = ZR_SYMBOL_TRIANGLE_LEFT; + sym.background = p->normal; + sym.foreground = p->text; + sym.border_width = 0; + + zr_draw_symbol(out, &sym, left, f); + sym.type = ZR_SYMBOL_TRIANGLE_RIGHT; + zr_draw_symbol(out, &sym, right, f); + + /* label */ + text.padding = zr_vec2(0,0); + text.background = p->normal; + text.text = p->text; + zr_widget_text(out, label, name, len, &text, ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, f); +} + +static float +zr_do_property(enum zr_widget_status *ws, + struct zr_command_buffer *out, struct zr_rect property, + const char *name, float min, float val, float max, + float step, float inc_per_pixel, char *buffer, zr_size *len, + int *state, zr_size *cursor, struct zr_property *p, zr_filter filter, + const struct zr_input *in, const struct zr_user_font *f) +{ + zr_size size; + char string[ZR_MAX_NUMBER_BUFFER]; + int active, old; + zr_size num_len, name_len; + float property_min; + float property_max; + float property_value; + zr_size *length; + char *dst = 0; + + struct zr_edit field; + struct zr_rect left; + struct zr_rect right; + struct zr_rect label; + struct zr_rect edit; + struct zr_rect empty; + + /* make sure the provided values are correct */ + property_max = MAX(min, max); + property_min = MIN(min, max); + property_value = CLAMP(property_min, val, property_max); + + /* left decrement button */ + left.h = f->height/2; + left.w = left.h; + left.x = property.x + p->border_size + p->padding.x; + left.y = property.y + p->border_size + property.h/2.0f - left.h/2; + + /* text label */ + name_len = zr_strsiz(name); + size = f->width(f->userdata, f->height, name, name_len); + label.x = left.x + left.w + p->padding.x; + label.w = (float)size + 2 * p->padding.x; + label.y = property.y + p->border_size; + label.h = property.h - 2 * p->border_size; + + /* right increment button */ + right.y = left.y; + right.w = left.w; + right.h = left.h; + right.x = property.x + property.w - (right.w + p->padding.x); + + /* edit */ + if (*state == ZR_PROPERTY_EDIT) { + size = f->width(f->userdata, f->height, buffer, *len); + length = len; + dst = buffer; + } else { + zr_ftos(string, property_value); + num_len = zr_string_float_limit(string, ZR_MAX_FLOAT_PRECISION); + size = f->width(f->userdata, f->height, string, num_len); + dst = string; + length = &num_len; + } + + edit.w = (float)size + 2 * p->padding.x; + edit.x = right.x - (edit.w + p->padding.x); + edit.y = property.y + p->border_size + 1; + edit.h = property.h - (2 * p->border_size + 2); + + /* empty left space activator */ + empty.w = edit.x - (label.x + label.w); + empty.x = label.x + label.w; + empty.y = property.y; + empty.h = property.h; + + old = (*state == ZR_PROPERTY_EDIT); + property_value = zr_property_behavior(ws, in, property, left, right, label, + edit, empty, state, property_min, property_value, + property_max, step, inc_per_pixel); + zr_property_draw(out, p, property, left, right, label, name, name_len, f); + + /* edit field */ + field.border_size = 0; + field.scrollbar_width = 0; + field.rounding = 0; + field.padding.x = 0; + field.padding.y = 0; + field.show_cursor = zr_true; + field.background = p->normal; + field.border = p->normal; + field.cursor = p->text; + field.text = p->text; + + active = (*state == ZR_PROPERTY_EDIT); + if (old != ZR_PROPERTY_EDIT && active) { + /* property has been activated so setup buffer */ + zr_memcopy(buffer, dst, *length); + *cursor = zr_utf_len(buffer, *length); + *len = *length; + length = len; + dst = buffer; + } + + *length = zr_widget_edit(out, edit, dst, *length, + ZR_MAX_NUMBER_BUFFER, &active, cursor, &field, filter, + (*state == ZR_PROPERTY_EDIT) ? in: 0, f); + if (active && zr_input_is_key_pressed(in, ZR_KEY_ENTER)) + active = !active; + + if (old && !active) { + /* property is now not active so convert edit text to value*/ + *state = ZR_PROPERTY_DEFAULT; + buffer[*len] = '\0'; + zr_string_float_limit(buffer, ZR_MAX_FLOAT_PRECISION); + zr_strtof(&property_value, buffer); + property_value = CLAMP(min, property_value, max); + } + return property_value; +} +/* ============================================================== + * + * INPUT + * + * ===============================================================*/ +void +zr_input_begin(struct zr_context *ctx) +{ + zr_size i; + struct zr_input *in; + ZR_ASSERT(ctx); + if (!ctx) return; + + in = &ctx->input; + for (i = 0; i < ZR_BUTTON_MAX; ++i) + in->mouse.buttons[i].clicked = 0; + in->keyboard.text_len = 0; + in->mouse.scroll_delta = 0; + zr_vec2_mov(in->mouse.prev, in->mouse.pos); + for (i = 0; i < ZR_KEY_MAX; i++) + in->keyboard.keys[i].clicked = 0; +} + +void +zr_input_motion(struct zr_context *ctx, int x, int y) +{ + struct zr_input *in; + ZR_ASSERT(ctx); + if (!ctx) return; + + in = &ctx->input; + in->mouse.pos.x = (float)x; + in->mouse.pos.y = (float)y; +} + +void +zr_input_key(struct zr_context *ctx, enum zr_keys key, int down) +{ + struct zr_input *in; + ZR_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + if (in->keyboard.keys[key].down == down) return; + + in->keyboard.keys[key].down = down; + in->keyboard.keys[key].clicked++; +} + +void +zr_input_button(struct zr_context *ctx, enum zr_buttons id, int x, int y, int down) +{ + struct zr_mouse_button *btn; + struct zr_input *in; + ZR_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + if (in->mouse.buttons[id].down == down) return; + + btn = &in->mouse.buttons[id]; + btn->clicked_pos.x = (float)x; + btn->clicked_pos.y = (float)y; + btn->down = down; + btn->clicked++; +} + +void +zr_input_scroll(struct zr_context *ctx, float y) +{ + ZR_ASSERT(ctx); + if (!ctx) return; + ctx->input.mouse.scroll_delta += y; +} + +void +zr_input_glyph(struct zr_context *ctx, const zr_glyph glyph) +{ + zr_size len = 0; + zr_rune unicode; + + struct zr_input *in; + ZR_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + + len = zr_utf_decode(glyph, &unicode, ZR_UTF_SIZE); + if (len && ((in->keyboard.text_len + len) < ZR_INPUT_MAX)) { + zr_utf_encode(unicode, &in->keyboard.text[in->keyboard.text_len], + ZR_INPUT_MAX - in->keyboard.text_len); + in->keyboard.text_len += len; + } +} + +void +zr_input_char(struct zr_context *ctx, char c) +{ + zr_glyph glyph; + ZR_ASSERT(ctx); + if (!ctx) return; + glyph[0] = c; + zr_input_glyph(ctx, glyph); +} + +void +zr_input_unicode(struct zr_context *ctx, zr_rune unicode) +{ + zr_glyph rune; + ZR_ASSERT(ctx); + if (!ctx) return; + zr_utf_encode(unicode, rune, ZR_UTF_SIZE); + zr_input_glyph(ctx, rune); +} + +void +zr_input_end(struct zr_context *ctx) +{ + struct zr_input *in; + ZR_ASSERT(ctx); + if (!ctx) return; + in = &ctx->input; + in->mouse.delta = zr_vec2_sub(in->mouse.pos, in->mouse.prev); +} + +int +zr_input_has_mouse_click_in_rect(const struct zr_input *i, enum zr_buttons id, + struct zr_rect b) +{ + const struct zr_mouse_button *btn; + if (!i) return zr_false; + btn = &i->mouse.buttons[id]; + if (!ZR_INBOX(btn->clicked_pos.x,btn->clicked_pos.y,b.x,b.y,b.w,b.h)) + return zr_false; + return zr_true; +} + +int +zr_input_has_mouse_click_down_in_rect(const struct zr_input *i, enum zr_buttons id, + struct zr_rect b, int down) +{ + const struct zr_mouse_button *btn; + if (!i) return zr_false; + btn = &i->mouse.buttons[id]; + return zr_input_has_mouse_click_in_rect(i, id, b) && (btn->down == down); +} + +int +zr_input_is_mouse_click_in_rect(const struct zr_input *i, enum zr_buttons id, + struct zr_rect b) +{ + const struct zr_mouse_button *btn; + if (!i) return zr_false; + btn = &i->mouse.buttons[id]; + return (zr_input_has_mouse_click_down_in_rect(i, id, b, zr_false) && + btn->clicked) ? zr_true : zr_false; +} + +int +zr_input_any_mouse_click_in_rect(const struct zr_input *in, struct zr_rect b) +{ + int i, down = 0; + for (i = 0; i < ZR_BUTTON_MAX; ++i) + down = down || zr_input_is_mouse_click_in_rect(in, (enum zr_buttons)i, b); + return down; +} + +int +zr_input_is_mouse_hovering_rect(const struct zr_input *i, struct zr_rect rect) +{ + if (!i) return zr_false; + return ZR_INBOX(i->mouse.pos.x, i->mouse.pos.y, rect.x, rect.y, rect.w, rect.h); +} + +int +zr_input_is_mouse_prev_hovering_rect(const struct zr_input *i, struct zr_rect rect) +{ + if (!i) return zr_false; + return ZR_INBOX(i->mouse.prev.x, i->mouse.prev.y, rect.x, rect.y, rect.w, rect.h); +} + +int +zr_input_mouse_clicked(const struct zr_input *i, enum zr_buttons id, struct zr_rect rect) +{ + if (!i) return zr_false; + if (!zr_input_is_mouse_hovering_rect(i, rect)) return zr_false; + return zr_input_is_mouse_click_in_rect(i, id, rect); +} + +int +zr_input_is_mouse_down(const struct zr_input *i, enum zr_buttons id) +{ + if (!i) return zr_false; + return i->mouse.buttons[id].down; +} + +int +zr_input_is_mouse_pressed(const struct zr_input *i, enum zr_buttons id) +{ + const struct zr_mouse_button *b; + if (!i) return zr_false; + b = &i->mouse.buttons[id]; + if (b->down && b->clicked) + return zr_true; + return zr_false; +} + +int +zr_input_is_mouse_released(const struct zr_input *i, enum zr_buttons id) +{ + if (!i) return zr_false; + return (!i->mouse.buttons[id].down && i->mouse.buttons[id].clicked); +} + +int +zr_input_is_key_pressed(const struct zr_input *i, enum zr_keys key) +{ + const struct zr_key *k; + if (!i) return zr_false; + k = &i->keyboard.keys[key]; + if (k->down && k->clicked) + return zr_true; + return zr_false; +} + +int +zr_input_is_key_released(const struct zr_input *i, enum zr_keys key) +{ + const struct zr_key *k; + if (!i) return zr_false; + k = &i->keyboard.keys[key]; + if (!k->down && k->clicked) + return zr_true; + return zr_false; +} + +int +zr_input_is_key_down(const struct zr_input *i, enum zr_keys key) +{ + const struct zr_key *k; + if (!i) return zr_false; + k = &i->keyboard.keys[key]; + if (k->down) return zr_true; + return zr_false; +} + +/* ============================================================== + * + * STYLE + * + * ===============================================================*/ +#define ZR_STYLE_PROPERTY_MAP(PROPERTY)\ + PROPERTY(ITEM_SPACING, 4.0f, 4.0f)\ + PROPERTY(ITEM_PADDING, 4.0f, 4.0f)\ + PROPERTY(TOUCH_PADDING, 0.0f, 0.0f)\ + PROPERTY(PADDING, 8.0f, 10.0f)\ + PROPERTY(SCALER_SIZE, 16.0f, 16.0f)\ + PROPERTY(SCROLLBAR_SIZE, 10.0f, 10.0f)\ + PROPERTY(SIZE, 64.0f, 64.0f) + +#define ZR_STYLE_ROUNDING_MAP(ROUNDING)\ + ROUNDING(BUTTON, 4.0f)\ + ROUNDING(SLIDER, 8.0f)\ + ROUNDING(CHECK, 0.0f)\ + ROUNDING(INPUT, 0.0f)\ + ROUNDING(PROPERTY, 10.0f)\ + ROUNDING(CHART, 4.0f)\ + ROUNDING(SCROLLBAR, 3.0f) + +#define ZR_STYLE_COLOR_MAP(COLOR)\ + COLOR(TEXT, 175, 175, 175, 255)\ + COLOR(TEXT_HOVERING, 120, 120, 120, 255)\ + COLOR(TEXT_ACTIVE, 100, 100, 100, 255)\ + COLOR(WINDOW, 45, 45, 45, 255)\ + COLOR(HEADER, 40, 40, 40, 255)\ + COLOR(BORDER, 65, 65, 65, 255)\ + COLOR(BUTTON, 50, 50, 50, 255)\ + COLOR(BUTTON_HOVER, 35, 35, 35, 255)\ + COLOR(BUTTON_ACTIVE, 40, 40, 40, 255)\ + COLOR(TOGGLE, 100, 100, 100, 255)\ + COLOR(TOGGLE_HOVER, 120, 120, 120, 255)\ + COLOR(TOGGLE_CURSOR, 45, 45, 45, 255)\ + COLOR(SELECTABLE, 100, 100, 100, 255)\ + COLOR(SELECTABLE_HOVER, 80, 80, 80, 255)\ + COLOR(SELECTABLE_TEXT, 45, 45, 45, 255)\ + COLOR(SLIDER, 38, 38, 38, 255)\ + COLOR(SLIDER_CURSOR, 100, 100, 100, 255)\ + COLOR(SLIDER_CURSOR_HOVER, 120, 120, 120, 255)\ + COLOR(SLIDER_CURSOR_ACTIVE, 150, 150, 150, 255)\ + COLOR(PROGRESS, 38, 38, 38, 255)\ + COLOR(PROGRESS_CURSOR, 100, 100, 100, 255)\ + COLOR(PROGRESS_CURSOR_HOVER, 120, 120, 120, 255)\ + COLOR(PROGRESS_CURSOR_ACTIVE, 150, 150, 150, 255)\ + COLOR(PROPERTY, 38, 38, 38, 255)\ + COLOR(PROPERTY_HOVER, 50, 50, 50, 255)\ + COLOR(PROPERTY_ACTIVE, 60, 60, 60, 255)\ + COLOR(INPUT, 45, 45, 45, 255)\ + COLOR(INPUT_CURSOR, 100, 100, 100, 255)\ + COLOR(INPUT_TEXT, 135, 135, 135, 255)\ + COLOR(COMBO, 45, 45, 45, 255)\ + COLOR(HISTO, 120, 120, 120, 255)\ + COLOR(HISTO_BARS, 45, 45, 45, 255)\ + COLOR(HISTO_HIGHLIGHT, 255, 0, 0, 255)\ + COLOR(PLOT, 120, 120, 120, 255)\ + COLOR(PLOT_LINES, 45, 45, 45, 255)\ + COLOR(PLOT_HIGHLIGHT, 255, 0, 0, 255)\ + COLOR(SCROLLBAR, 40, 40, 40, 255)\ + COLOR(SCROLLBAR_CURSOR, 100, 100, 100, 255)\ + COLOR(SCROLLBAR_CURSOR_HOVER, 120, 120, 120, 255)\ + COLOR(SCROLLBAR_CURSOR_ACTIVE, 150, 150, 150, 255)\ + COLOR(TABLE_LINES, 100, 100, 100, 255)\ + COLOR(TAB_HEADER, 40, 40, 40, 255)\ + COLOR(SCALER, 100, 100, 100, 255) + +static const char *zr_style_color_names[] = { + #define COLOR(a,b,c,d,e) #a, + ZR_STYLE_COLOR_MAP(COLOR) + #undef COLOR +}; +static const char *zr_style_rounding_names[] = { + #define ROUNDING(a,b) #a, + ZR_STYLE_ROUNDING_MAP(ROUNDING) + #undef ROUNDING +}; +static const char *zr_style_property_names[] = { + #define PROPERTY(a,b,c) #a, + ZR_STYLE_PROPERTY_MAP(PROPERTY) + #undef PROPERTY +}; + +const char* +zr_get_color_name(enum zr_style_colors color) +{return zr_style_color_names[color];} + +const char* +zr_get_rounding_name(enum zr_style_rounding rounding) +{return zr_style_rounding_names[rounding];} + +const char* +zr_get_property_name(enum zr_style_properties property) +{return zr_style_property_names[property];} + +static void +zr_style_default_properties(struct zr_style *style) +{ + #define PROPERTY(a,b,c) style->properties[ZR_PROPERTY_##a] = zr_vec2(b, c); + ZR_STYLE_PROPERTY_MAP(PROPERTY) + #undef PROPERTY +} + +static void +zr_style_default_rounding(struct zr_style *style) +{ + #define ROUNDING(a,b) style->rounding[ZR_ROUNDING_##a] = b; + ZR_STYLE_ROUNDING_MAP(ROUNDING) + #undef ROUNDING +} + +static void +zr_style_default_color(struct zr_style *style) +{ + #define COLOR(a,b,c,d,e) style->colors[ZR_COLOR_##a] = zr_rgba(b,c,d,e); + ZR_STYLE_COLOR_MAP(COLOR) + #undef COLOR +} + +void +zr_load_default_style(struct zr_context *ctx, zr_flags flags) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (flags & ZR_DEFAULT_COLOR) + zr_style_default_color(style); + if (flags & ZR_DEFAULT_PROPERTIES) + zr_style_default_properties(style); + if (flags & ZR_DEFAULT_ROUNDING) + zr_style_default_rounding(style); + + style->header.align = ZR_HEADER_RIGHT; + style->header.close_symbol = 'x'; + style->header.minimize_symbol = '-'; + style->header.maximize_symbol = '+'; +} + +void +zr_set_font(struct zr_context *ctx, const struct zr_user_font *font) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + style->font = *font; +} + +struct zr_vec2 +zr_get_property(const struct zr_context *ctx, enum zr_style_properties index) +{ + const struct zr_style *style; + static const struct zr_vec2 zero = {0,0}; + ZR_ASSERT(ctx); + if (!ctx) return zero; + style = &ctx->style; + return style->properties[index]; +} + +struct zr_color +zr_get_color(const struct zr_context *ctx, enum zr_style_colors index) +{ + const struct zr_style *style; + static const struct zr_color zero = {0,0,0,0}; + ZR_ASSERT(ctx); + if (!ctx) return zero; + style = &ctx->style; + return style->colors[index]; +} + +void +zr_push_color(struct zr_context *ctx, enum zr_style_colors index, + struct zr_color col) +{ + struct zr_style *style; + struct zr_saved_color *c; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (style->stack.color >= ZR_MAX_COLOR_STACK) return; + + c = &style->stack.colors[style->stack.color++]; + c->value = style->colors[index]; + c->type = index; + style->colors[index] = col; +} + +void +zr_push_property(struct zr_context *ctx, enum zr_style_properties index, + struct zr_vec2 v) +{ + struct zr_style *style; + struct zr_saved_property *p; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (style->stack.property >= ZR_MAX_ATTRIB_STACK) return; + + p = &style->stack.properties[style->stack.property++]; + p->value = style->properties[index]; + p->type = index; + style->properties[index] = v; +} + +void +zr_push_font(struct zr_context *ctx, struct zr_user_font font) +{ + struct zr_style *style; + struct zr_saved_font *f; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (style->stack.font >= ZR_MAX_FONT_STACK) return; + + f = &style->stack.fonts[style->stack.font++]; + f->font_height_begin = style->stack.font_height; + f->font_height_end = style->stack.font_height; + f->value = style->font; + style->font = font; +} + +void +zr_push_font_height(struct zr_context *ctx, float font_height) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (style->stack.font >= ZR_MAX_FONT_HEIGHT_STACK) return; + + style->stack.font_heights[style->stack.font_height++] = style->font.height; + if (style->stack.font) + style->stack.fonts[style->stack.font-1].font_height_end++; + style->font.height = font_height; +} + +void +zr_pop_color(struct zr_context *ctx) +{ + struct zr_style *style; + struct zr_saved_color *c; + ZR_ASSERT(ctx); + + if (!ctx) return; + style = &ctx->style; + if (!style->stack.color) return; + + c = &style->stack.colors[--style->stack.color]; + style->colors[c->type] = c->value; +} + +void +zr_pop_property(struct zr_context *ctx) +{ + struct zr_style *style; + struct zr_saved_property *p; + + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (!style->stack.property) return; + + p = &style->stack.properties[--style->stack.property]; + style->properties[p->type] = p->value; +} + +void +zr_pop_font(struct zr_context *ctx) +{ + struct zr_style *style; + struct zr_saved_font *f; + + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (!style->stack.font) return; + + f = &style->stack.fonts[--style->stack.font]; + style->stack.font_height = f->font_height_begin; + style->font = f->value; + if (style->stack.font_height) + style->font.height = style->stack.font_heights[style->stack.font_height-1]; +} + +void +zr_pop_font_height(struct zr_context *ctx) +{ + struct zr_style *style; + float font_height; + + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + if (!style->stack.font_height) return; + + font_height = style->stack.font_heights[--style->stack.font_height]; + style->font.height = font_height; + if (style->stack.font) { + ZR_ASSERT(style->stack.fonts[style->stack.font-1].font_height_end); + style->stack.fonts[style->stack.font-1].font_height_end--; + } +} + +void +zr_reset_colors(struct zr_context *ctx) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + while (style->stack.color) + zr_pop_color(ctx); +} + +void +zr_reset_properties(struct zr_context *ctx) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + while (style->stack.property) + zr_pop_property(ctx); +} + +void +zr_reset_font(struct zr_context *ctx) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + while (style->stack.font) + zr_pop_font(ctx); +} + +void +zr_reset_font_height(struct zr_context *ctx) +{ + struct zr_style *style; + ZR_ASSERT(ctx); + if (!ctx) return; + style = &ctx->style; + while (style->stack.font_height) + zr_pop_font_height(ctx); +} + +void +zr_reset(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + if (!ctx) return; + zr_reset_colors(ctx); + zr_reset_properties(ctx); + zr_reset_font(ctx); + zr_reset_font_height(ctx); +} + +/* =============================================================== + * + * POOL + * + * ===============================================================*/ +static void +zr_pool_init(struct zr_pool *pool, struct zr_allocator *alloc, + unsigned int capacity) +{ + zr_zero(pool, sizeof(*pool)); + pool->alloc = *alloc; + pool->capacity = capacity; + pool->pages = 0; + pool->type = ZR_BUFFER_DYNAMIC; +} + +static void +zr_pool_free(struct zr_pool *pool) +{ + struct zr_window_page *next; + struct zr_window_page *iter = pool->pages; + if (!pool) return; + if (pool->type == ZR_BUFFER_FIXED) return; + while (iter) { + next = iter->next; + pool->alloc.free(pool->alloc.userdata, iter); + iter = next; + } + pool->alloc.free(pool->alloc.userdata, pool); +} + +static void +zr_pool_init_fixed(struct zr_pool *pool, void *memory, zr_size size) +{ + zr_zero(pool, sizeof(*pool)); + /* make sure pages have correct granularity to at least fit one page into memory */ + if (size < sizeof(struct zr_window_page) + ZR_POOL_DEFAULT_CAPACITY * sizeof(struct zr_window)) + pool->capacity = (unsigned)(size - sizeof(struct zr_window_page)) / sizeof(struct zr_window); + else pool->capacity = ZR_POOL_DEFAULT_CAPACITY; + pool->pages = (struct zr_window_page*)memory; + pool->type = ZR_BUFFER_FIXED; + pool->size = size; +} + +static void* +zr_pool_alloc(struct zr_pool *pool) +{ + if (!pool->pages || pool->pages->size >= pool->capacity) { + /* allocate new page */ + struct zr_window_page *page; + if (pool->type == ZR_BUFFER_FIXED) { + if (!pool->pages) { + ZR_ASSERT(pool->pages); + return 0; + } + ZR_ASSERT(pool->pages->size < pool->capacity); + return 0; + } else { + zr_size size = sizeof(struct zr_window_page); + size += ZR_POOL_DEFAULT_CAPACITY * sizeof(union zr_page_data); + page = (struct zr_window_page*)pool->alloc.alloc(pool->alloc.userdata, size); + page->size = 0; + page->next = pool->pages; + pool->pages = page; + } + } + return &pool->pages->win[pool->pages->size++]; +} + +/* =============================================================== + * + * CONTEXT + * + * ===============================================================*/ +static void* zr_create_window(struct zr_context *ctx); +static void zr_free_window(struct zr_context *ctx, struct zr_window *win); +static void zr_free_table(struct zr_context *ctx, struct zr_table *tbl); +static void zr_remove_table(struct zr_window *win, struct zr_table *tbl); + +static void +zr_setup(struct zr_context *ctx, const struct zr_user_font *font) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(font); + if (!ctx || !font) return; + zr_zero_struct(*ctx); + zr_load_default_style(ctx, ZR_DEFAULT_ALL); + ctx->style.font = *font; +#if ZR_COMPILE_WITH_VERTEX_BUFFER + zr_canvas_init(&ctx->canvas); +#endif +} + +int +zr_init_fixed(struct zr_context *ctx, void *memory, zr_size size, + const struct zr_user_font *font) +{ + ZR_ASSERT(memory); + if (!memory) return 0; + zr_setup(ctx, font); + zr_buffer_init_fixed(&ctx->memory, memory, size); + ctx->pool = 0; + return 1; +} + +int +zr_init_custom(struct zr_context *ctx, struct zr_buffer *cmds, + struct zr_buffer *pool, const struct zr_user_font *font) +{ + ZR_ASSERT(cmds); + ZR_ASSERT(pool); + if (!cmds || !pool) return 0; + zr_setup(ctx, font); + ctx->memory = *cmds; + if (pool->type == ZR_BUFFER_FIXED) { + /* take memory from buffer and alloc fixed pool */ + void *memory = pool->memory.ptr; + zr_size size = pool->memory.size; + ctx->pool = memory; + ZR_ASSERT(size > sizeof(struct zr_pool)); + size -= sizeof(struct zr_pool); + zr_pool_init_fixed((struct zr_pool*)ctx->pool, + (void*)((zr_byte*)ctx->pool+sizeof(struct zr_pool)), size); + } else { + /* create dynamic pool from buffer allocator */ + struct zr_allocator *alloc = &pool->pool; + ctx->pool = alloc->alloc(alloc->userdata, sizeof(struct zr_pool)); + zr_pool_init((struct zr_pool*)ctx->pool, alloc, ZR_POOL_DEFAULT_CAPACITY); + } + return 1; +} + +int +zr_init(struct zr_context *ctx, struct zr_allocator *alloc, + const struct zr_user_font *font) +{ + ZR_ASSERT(alloc); + if (!alloc) return 0; + zr_setup(ctx, font); + zr_buffer_init(&ctx->memory, alloc, ZR_DEFAULT_COMMAND_BUFFER_SIZE); + ctx->pool = alloc->alloc(alloc->userdata, sizeof(struct zr_pool)); + zr_pool_init((struct zr_pool*)ctx->pool, alloc, ZR_POOL_DEFAULT_CAPACITY); + return 1; +} + +#if ZR_COMPILE_WITH_COMMAND_USERDATA +void +zr_set_user_data(struct zr_context *ctx, zr_handle handle) +{ + if (!ctx) return; + ctx->userdata = handle; + if (ctx->current) + ctx->current->buffer.userdata = handle; +} +#endif + +void +zr_free(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + if (!ctx) return; + zr_buffer_free(&ctx->memory); + if (ctx->pool) zr_pool_free((struct zr_pool*)ctx->pool); + + zr_zero(&ctx->input, sizeof(ctx->input)); + zr_zero(&ctx->style, sizeof(ctx->style)); + zr_zero(&ctx->memory, sizeof(ctx->memory)); + + ctx->seq = 0; + ctx->pool = 0; + ctx->build = 0; + ctx->begin = 0; + ctx->end = 0; + ctx->active = 0; + ctx->current = 0; + ctx->freelist = 0; + ctx->count = 0; +} + +void +zr_clear(struct zr_context *ctx) +{ + struct zr_window *iter; + struct zr_window *next; + ZR_ASSERT(ctx); + + if (!ctx) return; + if (ctx->pool) + zr_buffer_clear(&ctx->memory); + else zr_buffer_reset(&ctx->memory, ZR_BUFFER_FRONT); + + ctx->build = 0; + ctx->memory.calls = 0; +#if ZR_COMPILE_WITH_VERTEX_BUFFER + zr_canvas_clear(&ctx->canvas); +#endif + + /* garbage collector */ + iter = ctx->begin; + while (iter) { + /* make sure minimized windows do not get removed */ + if (iter->flags & ZR_WINDOW_MINIMIZED) { + iter = iter->next; + continue; + } + + /* free unused popup windows */ + if (iter->popup.win && iter->popup.win->seq != ctx->seq) { + zr_free_window(ctx, iter->popup.win); + iter->popup.win = 0; + } + + {struct zr_table *n, *it = iter->tables; + while (it) { + /* remove unused window state tables */ + n = it->next; + if (it->seq != ctx->seq) { + zr_remove_table(iter, it); + zr_zero(it, sizeof(union zr_page_data)); + zr_free_table(ctx, it); + if (it == iter->tables) + iter->tables = n; + } + it = n; + }} + + /* window itself is not used anymore so free */ + if (iter->seq != ctx->seq) { + next = iter->next; + zr_free_window(ctx, iter); + ctx->count--; + iter = next; + } else iter = iter->next; + } + ctx->seq++; +} + +/* ---------------------------------------------------------------- + * + * BUFFERING + * + * ---------------------------------------------------------------*/ +static void +zr_start(struct zr_context *ctx, struct zr_window *win) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(win); + if (!ctx || !win) return; + win->buffer.begin = ctx->memory.allocated; + win->buffer.end = win->buffer.begin; + win->buffer.last = win->buffer.begin; + win->buffer.clip = zr_null_rect; +} + +static void +zr_start_popup(struct zr_context *ctx, struct zr_window *win) +{ + struct zr_popup_buffer *buf; + struct zr_panel *iter; + ZR_ASSERT(ctx); + ZR_ASSERT(win); + if (!ctx || !win) return; + + /* make sure to use the correct popup buffer*/ + iter = win->layout; + while (iter->parent) + iter = iter->parent; + + /* save buffer fill state for popup */ + buf = &iter->popup_buffer; + buf->begin = win->buffer.end; + buf->end = win->buffer.end; + buf->parent = win->buffer.last; + buf->last = buf->begin; + buf->active = zr_true; +} + +static void +zr_finish_popup(struct zr_context *ctx, struct zr_window *win) +{ + struct zr_popup_buffer *buf; + struct zr_panel *iter; + ZR_ASSERT(ctx); + ZR_ASSERT(win); + if (!ctx || !win) return; + + /* make sure to use the correct popup buffer*/ + iter = win->layout; + while (iter->parent) + iter = iter->parent; + + buf = &iter->popup_buffer; + buf->last = win->buffer.last; + buf->end = win->buffer.end; +} + +static void +zr_finish(struct zr_context *ctx, struct zr_window *win) +{ + struct zr_popup_buffer *buf; + struct zr_command *parent_last; + struct zr_command *sublast; + struct zr_command *last; + void *memory; + + ZR_ASSERT(ctx); + ZR_ASSERT(win); + if (!ctx || !win) return; + win->buffer.end = ctx->memory.allocated; + if (!win->layout->popup_buffer.active) return; + + /* frome here this case is for popup windows */ + buf = &win->layout->popup_buffer; + memory = ctx->memory.memory.ptr; + + /* redirect the sub-window buffer to the end of the window command buffer */ + parent_last = zr_ptr_add(struct zr_command, memory, buf->parent); + sublast = zr_ptr_add(struct zr_command, memory, buf->last); + last = zr_ptr_add(struct zr_command, memory, win->buffer.last); + + parent_last->next = buf->end; + sublast->next = last->next; + last->next = buf->begin; + win->buffer.last = buf->last; + win->buffer.end = buf->end; + buf->active = zr_false; +} + +static void +zr_build(struct zr_context *ctx) +{ + struct zr_window *iter; + struct zr_window *next; + struct zr_command *cmd; + zr_byte *buffer; + + iter = ctx->begin; + buffer = (zr_byte*)ctx->memory.memory.ptr; + while (iter != 0) { + next = iter->next; + if (iter->buffer.last != iter->buffer.begin) { + cmd = zr_ptr_add(struct zr_command, buffer, iter->buffer.last); + while (next && next->buffer.last == next->buffer.begin) + next = next->next; /* skip empty command buffers */ + + if (next) { + cmd->next = next->buffer.begin; + } else cmd->next = ctx->memory.allocated; + } + iter = next; + } +} + +const struct zr_command* +zr__begin(struct zr_context *ctx) +{ + struct zr_window *iter; + zr_byte *buffer; + ZR_ASSERT(ctx); + if (!ctx) return 0; + if (!ctx->count) return 0; + + /* build one command list out of all windows */ + buffer = (zr_byte*)ctx->memory.memory.ptr; + if (!ctx->build) { + zr_build(ctx); + ctx->build = zr_true; + } + + iter = ctx->begin; + while (iter && iter->buffer.begin == iter->buffer.end) + iter = iter->next; + if (!iter) return 0; + return zr_ptr_add_const(struct zr_command, buffer, iter->buffer.begin); +} + +const struct zr_command* +zr__next(struct zr_context *ctx, const struct zr_command *cmd) +{ + zr_byte *buffer; + const struct zr_command *next; + ZR_ASSERT(ctx); + if (!ctx || !cmd || !ctx->count) return 0; + if (cmd->next >= ctx->memory.allocated) return 0; + buffer = (zr_byte*)ctx->memory.memory.ptr; + next = zr_ptr_add_const(struct zr_command, buffer, cmd->next); + return next; +} + +/* ---------------------------------------------------------------- + * + * TABLES + * + * ---------------------------------------------------------------*/ +static struct zr_table* +zr_create_table(struct zr_context *ctx) +{void *tbl = (void*)zr_create_window(ctx); return (struct zr_table*)tbl;} + +static void +zr_free_table(struct zr_context *ctx, struct zr_table *tbl) +{zr_free_window(ctx, (struct zr_window*)tbl);} + +static void +zr_push_table(struct zr_window *win, struct zr_table *tbl) +{ + if (!win->tables) { + win->tables = tbl; + tbl->next = 0; + tbl->prev = 0; + win->table_count = 1; + win->table_size = 1; + return; + } + win->tables->prev = tbl; + tbl->next = win->tables; + tbl->prev = 0; + win->tables = tbl; + win->table_count++; + win->table_size = 0; +} + +static void +zr_remove_table(struct zr_window *win, struct zr_table *tbl) +{ + if (win->tables == tbl) + win->tables = tbl->next; + if (tbl->next) + tbl->next->prev = tbl->prev; + if (tbl->prev) + tbl->prev->next = tbl->next; + tbl->next = 0; + tbl->prev = 0; +} + +static zr_uint* +zr_add_value(struct zr_context *ctx, struct zr_window *win, + zr_hash name, zr_uint value) +{ + if (!win->tables || win->table_size == ZR_VALUE_PAGE_CAPACITY) { + struct zr_table *tbl = zr_create_table(ctx); + zr_push_table(win, tbl); + } + win->tables->seq = win->seq; + win->tables->keys[win->table_size] = name; + win->tables->values[win->table_size] = value; + return &win->tables->values[win->table_size++]; +} + +static zr_uint* +zr_find_value(struct zr_window *win, zr_hash name) +{ + unsigned short size = win->table_size; + struct zr_table *iter = win->tables; + while (iter) { + unsigned short i = 0; + for (i = 0; i < size; ++i) { + if (iter->keys[i] == name) { + iter->seq = win->seq; + return &iter->values[i]; + } + } + size = ZR_VALUE_PAGE_CAPACITY; + iter = iter->next; + } + return 0; +} +/* ---------------------------------------------------------------- + * + * WINDOW + * + * ---------------------------------------------------------------*/ +static int zr_panel_begin(struct zr_context *ctx, const char *title); +static void zr_panel_end(struct zr_context *ctx); + +static void* +zr_create_window(struct zr_context *ctx) +{ + struct zr_window *win = 0; + if (ctx->freelist) { + /* unlink window from free list */ + win = ctx->freelist; + ctx->freelist = win->next; + } else if (ctx->pool) { + /* allocate window from memory pool */ + win = (struct zr_window*) zr_pool_alloc((struct zr_pool*)ctx->pool); + ZR_ASSERT(win); + if (!win) return 0; + } else { + /* allocate new window from the back of the fixed size memory buffer */ + static const zr_size size = sizeof(union zr_page_data); + static const zr_size align = ZR_ALIGNOF(union zr_page_data); + win = (struct zr_window*)zr_buffer_alloc(&ctx->memory, ZR_BUFFER_BACK, size, align); + ZR_ASSERT(win); + if (!win) return 0; + } + zr_zero(win, sizeof(union zr_page_data)); + win->seq = ctx->seq; + return win; +} + +static void +zr_free_window(struct zr_context *ctx, struct zr_window *win) +{ + /* unlink windows from list */ + struct zr_table *n, *it = win->tables; + + if (win == ctx->begin) { + ctx->begin = win->next; + if (win->next) + ctx->begin->prev = 0; + } else if (win == ctx->end) { + ctx->end = win->prev; + if (win->prev) + ctx->end->next = 0; + } else { + if (win->next) + win->next->prev = win->next; + if (win->prev) + win->prev->next = win->prev; + } + if (win->popup.win) { + zr_free_window(ctx, win->popup.win); + win->popup.win = 0; + } + + win->next = 0; + win->prev = 0; + + while (it) { + /*free window state tables */ + n = it->next; + if (it->seq != ctx->seq) { + zr_remove_table(win, it); + zr_free_table(ctx, it); + if (it == win->tables) + win->tables = n; + } + it = n; + } + + /* link windows into freelist */ + if (!ctx->freelist) { + ctx->freelist = win; + } else { + win->next = ctx->freelist; + ctx->freelist = win; + } +} + +static struct zr_window* +zr_find_window(struct zr_context *ctx, zr_hash hash) +{ + struct zr_window *iter; + iter = ctx->begin; + while (iter) { + if (iter->name == hash) + return iter; + iter = iter->next; + } + return 0; +} + +static void +zr_insert_window(struct zr_context *ctx, struct zr_window *win) +{ + struct zr_window *end; + ZR_ASSERT(ctx); + ZR_ASSERT(win); + if (!win || !ctx) return; + + if (!ctx->begin) { + win->next = 0; + win->prev = 0; + ctx->begin = win; + ctx->end = win; + ctx->count = 1; + return; + } + + end = ctx->end; + end->next = win; + win->prev = ctx->end; + win->next = 0; + ctx->end = win; + ctx->count++; +} + +static void +zr_remove_window(struct zr_context *ctx, struct zr_window *win) +{ + if (win->prev) + win->prev->next = win->next; + if (win->next) + win->next->prev = win->prev; + if (ctx->begin == win) + ctx->begin = win->next; + if (ctx->end == win) + ctx->end = win->prev; + + win->next = 0; + win->prev = 0; + ctx->count--; +} + +int +zr_begin(struct zr_context *ctx, struct zr_panel *layout, + const char *title, struct zr_rect bounds, zr_flags flags) +{ + struct zr_window *win; + zr_hash title_hash; + int title_len; + int ret = 0; + + ZR_ASSERT(ctx); + ZR_ASSERT(title); + ZR_ASSERT(!ctx->current && "if this triggers you missed a `zr_end` call"); + if (!ctx || ctx->current || !title) + return 0; + + /* find or create window */ + title_len = (int)zr_strsiz(title); + title_hash = zr_murmur_hash(title, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + if (!win) { + win = (struct zr_window*)zr_create_window(ctx); + zr_insert_window(ctx, win); + zr_command_buffer_init(&win->buffer, &ctx->memory, ZR_CLIPPING_ON); + ZR_ASSERT(win); + if (!win) return 0; + + win->flags = flags; + win->bounds = bounds; + win->name = title_hash; + win->popup.win = 0; + } else { + /* update public window flags */ + win->flags &= ~(zr_flags)(ZR_WINDOW_PRIVATE-1); + win->flags |= flags; + win->seq++; + } + if (win->flags & ZR_WINDOW_HIDDEN) return 0; + + /* overlapping window */ + if (!(win->flags & ZR_WINDOW_SUB) && !(win->flags & ZR_WINDOW_HIDDEN)) + { + int inpanel, ishovered; + const struct zr_window *iter = win; + + /* This is so terrible but neccessary for minimized windows. The difference + * lies in the size of the window. But it is not possible to get the size + * without cheating because I do not have the information at this point. + * Even worse this is wrong since windows could have different window heights. + * I leave it in for now since I otherwise loose my mind right now. */ + struct zr_vec2 window_padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + struct zr_vec2 item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + float h = ctx->style.font.height + 2 * item_padding.y + window_padding.y; + + /* activate window if hovered and no other window is overlapping this window */ + zr_start(ctx, win); + inpanel = zr_input_mouse_clicked(&ctx->input, ZR_BUTTON_LEFT, win->bounds); + ishovered = zr_input_is_mouse_hovering_rect(&ctx->input, win->bounds); + if ((win != ctx->active) && ishovered) { + iter = win->next; + while (iter) { + if (!(iter->flags & ZR_WINDOW_MINIMIZED)) { + if (ZR_INTERSECT(win->bounds.x, win->bounds.y, win->bounds.w, win->bounds.h, + iter->bounds.x, iter->bounds.y, iter->bounds.w, iter->bounds.h) && + !(iter->flags & ZR_WINDOW_HIDDEN)) + break; + } else { + if (ZR_INTERSECT(win->bounds.x, win->bounds.y, win->bounds.w, win->bounds.h, + iter->bounds.x, iter->bounds.y, iter->bounds.w, h) && + !(iter->flags & ZR_WINDOW_HIDDEN)) + break; + } + if (iter->popup.win && iter->popup.active && !(iter->flags & ZR_WINDOW_HIDDEN) && + ZR_INTERSECT(win->bounds.x, win->bounds.y, win->bounds.w, win->bounds.h, + iter->popup.win->bounds.x, iter->popup.win->bounds.y, + iter->popup.win->bounds.w, iter->popup.win->bounds.h)) + break; + iter = iter->next; + } + } + + /* activate window if clicked */ + if (iter && inpanel && (win != ctx->end)) { + iter = win->next; + while (iter) { + /* try to find a panel with higher priorty in the same position */ + if (!(iter->flags & ZR_WINDOW_MINIMIZED)) { + if (ZR_INBOX(ctx->input.mouse.prev.x, ctx->input.mouse.prev.y, iter->bounds.x, + iter->bounds.y, iter->bounds.w, iter->bounds.h) && + !(iter->flags & ZR_WINDOW_HIDDEN)) + break; + } else { + if (ZR_INBOX(ctx->input.mouse.prev.x, ctx->input.mouse.prev.y, iter->bounds.x, + iter->bounds.y, iter->bounds.w, h) && + !(iter->flags & ZR_WINDOW_HIDDEN)) + break; + } + if (iter->popup.win && iter->popup.active && !(iter->flags & ZR_WINDOW_HIDDEN) && + ZR_INTERSECT(win->bounds.x, win->bounds.y, win->bounds.w, win->bounds.h, + iter->popup.win->bounds.x, iter->popup.win->bounds.y, + iter->popup.win->bounds.w, iter->popup.win->bounds.h)) + break; + iter = iter->next; + } + } + + if (!iter) { + /* current window is active in that position so transfer to top + * at the highest priority in stack */ + zr_remove_window(ctx, win); + zr_insert_window(ctx, win); + + /* NOTE: I am not really sure if activating a window should directly + * happen on that frame or the following frame. Directly would simplify + * clicking window closing/minimizing but could cause wrong behavior. + * For now I activate the window on the next frame to prevent wrong + * behavior. If not wanted just replace line with: + * win->flags &= ~(zr_flags)ZR_WINDOW_ROM; */ + win->flags |= ZR_WINDOW_REMOVE_ROM; + ctx->active = win; + } + if (ctx->end != win) + win->flags |= ZR_WINDOW_ROM; + } + win->layout = layout; + ctx->current = win; + ret = zr_panel_begin(ctx, title); + layout->offset = &win->scrollbar; + return ret; +} + +void +zr_end(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + if (!ctx || !ctx->current) return; + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + zr_panel_end(ctx); + ctx->current = 0; +} + +struct zr_rect +zr_window_get_bounds(const struct zr_context *ctx) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return zr_rect(0,0,0,0); + return ctx->current->bounds; +} +struct zr_vec2 +zr_window_get_position(const struct zr_context *ctx) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return zr_vec2(0,0); + return zr_vec2(ctx->current->bounds.x, ctx->current->bounds.y); +} + +struct zr_vec2 +zr_window_get_size(const struct zr_context *ctx) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return zr_vec2(0,0); + return zr_vec2(ctx->current->bounds.w, ctx->current->bounds.h); +} + +float +zr_window_get_width(const struct zr_context *ctx) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return 0; + return ctx->current->bounds.w; +} + +float +zr_window_get_height(const struct zr_context *ctx) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return 0; + return ctx->current->bounds.h; +} + +struct zr_rect +zr_window_get_content_region(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return zr_rect(0,0,0,0); + return ctx->current->layout->clip; +} + +struct zr_vec2 +zr_window_get_content_region_min(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return zr_vec2(0,0); + return zr_vec2(ctx->current->layout->clip.x, ctx->current->layout->clip.y); +} + +struct zr_vec2 +zr_window_get_content_region_max(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return zr_vec2(0,0); + return zr_vec2(ctx->current->layout->clip.x + ctx->current->layout->clip.w, + ctx->current->layout->clip.y + ctx->current->layout->clip.h); +} + +struct zr_vec2 +zr_window_get_content_region_size(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return zr_vec2(0,0); + return zr_vec2(ctx->current->layout->clip.w, ctx->current->layout->clip.h); +} + +struct zr_command_buffer* +zr_window_get_canvas(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return 0; + return &ctx->current->buffer; +} + +int +zr_window_has_focus(const struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current) return 0; + return ctx->current == ctx->active; +} + +int +zr_window_is_collapsed(struct zr_context *ctx, const char *name) +{ + int title_len; + zr_hash title_hash; + struct zr_window *win; + ZR_ASSERT(ctx); + if (!ctx) return 0; + + title_len = (int)zr_strsiz(name); + title_hash = zr_murmur_hash(name, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + if (!win) return 0; + return win->flags & ZR_WINDOW_MINIMIZED; +} + +int +zr_window_is_closed(struct zr_context *ctx, const char *name) +{ + int title_len; + zr_hash title_hash; + struct zr_window *win; + ZR_ASSERT(ctx); + if (!ctx) return 0; + + title_len = (int)zr_strsiz(name); + title_hash = zr_murmur_hash(name, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + if (!win) return 0; + return win->flags & ZR_WINDOW_HIDDEN; +} + +int +zr_window_is_active(struct zr_context *ctx, const char *name) +{ + int title_len; + zr_hash title_hash; + struct zr_window *win; + ZR_ASSERT(ctx); + if (!ctx) return 0; + + title_len = (int)zr_strsiz(name); + title_hash = zr_murmur_hash(name, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + if (!win) return 0; + return win == ctx->active; +} + +void +zr_window_set_bounds(struct zr_context *ctx, struct zr_rect bounds) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + ctx->current->bounds = bounds; +} + +void +zr_window_set_position(struct zr_context *ctx, struct zr_vec2 pos) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + ctx->current->bounds.x = pos.x; + ctx->current->bounds.y = pos.y; +} + +void +zr_window_set_size(struct zr_context *ctx, struct zr_vec2 size) +{ + ZR_ASSERT(ctx); ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) return; + ctx->current->bounds.w = size.x; + ctx->current->bounds.h = size.y; +} + +void +zr_window_collapse(struct zr_context *ctx, const char *name, + enum zr_collapse_states c) +{ + int title_len; + zr_hash title_hash; + struct zr_window *win; + ZR_ASSERT(ctx); + if (!ctx) return; + + title_len = (int)zr_strsiz(name); + title_hash = zr_murmur_hash(name, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + if (!win) return; + if (c == ZR_MINIMIZED) + win->flags |= ZR_WINDOW_MINIMIZED; + else win->flags &= ~(zr_flags)ZR_WINDOW_MINIMIZED; +} + +void +zr_window_collapse_if(struct zr_context *ctx, const char *name, + enum zr_collapse_states c, int cond) +{ + int title_len; + zr_hash title_hash; + struct zr_window *win; + ZR_ASSERT(ctx); + if (!ctx || !cond) return; + + title_len = (int)zr_strsiz(name); + title_hash = zr_murmur_hash(name, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + if (!win) return; + + if (c == ZR_MINIMIZED) + win->flags |= ZR_WINDOW_HIDDEN; + else win->flags &= ~(zr_flags)ZR_WINDOW_HIDDEN; +} + +void +zr_window_set_focus(struct zr_context *ctx, const char *name) +{ + int title_len; + zr_hash title_hash; + struct zr_window *win; + ZR_ASSERT(ctx); + if (!ctx) return; + + title_len = (int)zr_strsiz(name); + title_hash = zr_murmur_hash(name, (int)title_len, ZR_WINDOW_TITLE); + win = zr_find_window(ctx, title_hash); + ctx->active = win; +} + +/*---------------------------------------------------------------- + * + * PANEL + * + * --------------------------------------------------------------*/ +struct zr_window_header { + float x, y, w, h; + float front, back; +}; + +static int +zr_header_button(struct zr_context *ctx, struct zr_window_header *header, + zr_rune symbol, enum zr_style_header_align align) +{ + /* calculate the position of the close icon position and draw it */ + zr_glyph glyph; + struct zr_rect sym = {0,0,0,0}; + float sym_bw = 0; + int ret = zr_false; + + const struct zr_style *c; + struct zr_command_buffer *out; + struct zr_vec2 item_padding; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + /* cache configuration data */ + win = ctx->current; + layout = win->layout; + c = &ctx->style; + out = &win->buffer; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + + sym.x = header->front; + sym.y = header->y; + { + /* single unicode rune text icon */ + const char *X = glyph; + const zr_size len = zr_utf_encode(symbol, glyph, sizeof(glyph)); + const zr_size t = c->font.width(c->font.userdata, c->font.height, X, len); + const float text_width = (float)t; + struct zr_text text; + + /* calculate bounds of the icon */ + sym_bw = text_width; + sym.w = (float)text_width + 2 * item_padding.x; + sym.h = c->font.height + 2 * item_padding.y; + if (align == ZR_HEADER_RIGHT) + sym.x = header->back - sym.w; + + text.padding = zr_vec2(0,0); + text.background = c->colors[ZR_COLOR_HEADER]; + text.text = c->colors[ZR_COLOR_TEXT]; + zr_widget_text(out, sym, X, len, &text, + ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, &c->font); + } + + /* check if the icon has been pressed */ + if (!(layout->flags & ZR_WINDOW_ROM)) { + struct zr_rect bounds; + enum zr_widget_status status; + bounds.x = sym.x; bounds.y = sym.y; + bounds.w = sym_bw; bounds.h = sym.h; + ret = zr_button_behavior(&status, bounds, &ctx->input, ZR_BUTTON_DEFAULT); + } + + /* update the header space */ + if (align == ZR_HEADER_RIGHT) + header->back -= (sym.w + item_padding.x); + else header->front += sym.w + item_padding.x; + return ret; +} + +static int +zr_header_toggle(struct zr_context *ctx, struct zr_window_header *header, + zr_rune active, zr_rune inactive, enum zr_style_header_align align, int state) +{ + int ret = zr_header_button(ctx, header,(state) ? active : inactive, align); + if (ret) return !state; + else return state; +} + +static int +zr_header_flag(struct zr_context *ctx, struct zr_window_header *header, + zr_rune inactive, zr_rune active, enum zr_style_header_align align, + zr_flags flag) +{ + struct zr_window *win = ctx->current; + struct zr_panel *layout = win->layout; + zr_flags flags = win->flags; + int state = (flags & flag) ? zr_true : zr_false; + int ret = zr_header_toggle(ctx, header, inactive, active, align, state); + if (ret != ((flags & flag) ? zr_true : zr_false)) { + /* the state of the toggle icon has been changed */ + if (!ret) layout->flags &= ~flag; + else layout->flags |= flag; + return zr_true; + } + return zr_false; +} + +static int +zr_panel_begin(struct zr_context *ctx, const char *title) +{ + int header_active = 0; + float scrollbar_size; + struct zr_vec2 item_padding; + struct zr_vec2 item_spacing; + struct zr_vec2 window_padding; + struct zr_vec2 scaler_size; + + struct zr_input *in; + struct zr_window *win; + const struct zr_style *c; + struct zr_panel *layout; + struct zr_command_buffer *out; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + c = &ctx->style; + in = &ctx->input; + win = ctx->current; + layout = win->layout; + + /* cache style data */ + scrollbar_size = zr_get_property(ctx, ZR_PROPERTY_SCROLLBAR_SIZE).x; + window_padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + item_spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + scaler_size = zr_get_property(ctx, ZR_PROPERTY_SCALER_SIZE); + + /* check arguments */ + zr_zero(layout, sizeof(*layout)); + if (win->flags & ZR_WINDOW_HIDDEN) + return 0; + + /* move panel position if requested */ + layout->header_h = c->font.height + 4 * item_padding.y; + layout->header_h += window_padding.y; + if ((win->flags & ZR_WINDOW_MOVABLE) && !(win->flags & ZR_WINDOW_ROM)) { + int incursor; + struct zr_rect move; + move.x = win->bounds.x; + move.y = win->bounds.y; + move.w = win->bounds.w; + move.h = layout->header_h; + incursor = zr_input_is_mouse_prev_hovering_rect(in, move); + if (zr_input_is_mouse_down(in, ZR_BUTTON_LEFT) && incursor) { + win->bounds.x = win->bounds.x + in->mouse.delta.x; + win->bounds.y = win->bounds.y + in->mouse.delta.y; + } + } + +#if ZR_COMPILE_WITH_COMMAND_USERDATA + win->buffer.userdata = ctx->userdata; +#endif + + /* setup window layout */ + out = &win->buffer; + layout->bounds = win->bounds; + layout->at_x = win->bounds.x; + layout->at_y = win->bounds.y; + layout->width = win->bounds.w; + layout->height = win->bounds.h; + layout->row.index = 0; + layout->row.columns = 0; + layout->row.height = 0; + layout->row.ratio = 0; + layout->row.item_width = 0; + layout->row.tree_depth = 0; + layout->max_x = 0; + layout->flags = win->flags; + + /* calculate window header */ + if (win->flags & ZR_WINDOW_MINIMIZED) { + layout->header_h = 0; + layout->row.height = 0; + } else { + layout->header_h = 2 * item_spacing.y; + layout->row.height = layout->header_h + 1; + } + + /* calculate window footer height */ + if (!(win->flags & ZR_WINDOW_NONBLOCK) && + (!(win->flags & ZR_WINDOW_NO_SCROLLBAR) || (win->flags & ZR_WINDOW_SCALABLE))) + layout->footer_h = scaler_size.y + item_padding.y; + else layout->footer_h = 0; + + /* calculate the window size */ + if (!(win->flags & ZR_WINDOW_NO_SCROLLBAR)) + layout->width = win->bounds.w - scrollbar_size; + layout->height = win->bounds.h - (layout->header_h + 2 * item_spacing.y); + layout->height -= layout->footer_h; + + /* window header */ + header_active = (win->flags & (ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE)); + header_active = header_active || (win->flags & ZR_WINDOW_TITLE); + header_active = header_active && !(win->flags & ZR_WINDOW_HIDDEN) && title; + + if (header_active) + { + struct zr_rect old_clip = out->clip; + struct zr_window_header header; + + /* This is a little bit of a performace hack. To make sure the header + * does not get overdrawn with text you do not have to push a scissor rect. + * This is possible because the command buffer automatically clips text + * by using its clipping rectangle. But since the clipping rect gets + * reused to calculate the window clipping rect the old clipping rect + * has to be stored and reset afterwards. */ + out->clip.x = header.x = layout->bounds.x + window_padding.x; + out->clip.y = header.y = layout->bounds.y + item_padding.y; + out->clip.w = header.w = MAX(layout->bounds.w, 2 * window_padding.x); + out->clip.h = header.w -= 2 * window_padding.x; + + /* update the header height and first row height */ + layout->header_h = c->font.height + 2 * item_padding.y; + layout->header_h += window_padding.y; + layout->row.height += layout->header_h; + + header.h = layout->header_h; + header.back = header.x + header.w; + header.front = header.x; + + layout->height = layout->bounds.h - (header.h + 2 * item_spacing.y); + layout->height -= layout->footer_h; + + /* draw header background */ + if (!(layout->flags & ZR_WINDOW_BORDER)) { + zr_draw_rect(out, zr_rect(layout->bounds.x, layout->bounds.y, + layout->bounds.w, layout->header_h), 0, c->colors[ZR_COLOR_HEADER]); + } else { + zr_draw_rect(out, zr_rect(layout->bounds.x, layout->bounds.y+1, + layout->bounds.w, layout->header_h), 0, c->colors[ZR_COLOR_HEADER]); + } + + /* window header icons */ + if (win->flags & ZR_WINDOW_CLOSABLE) + zr_header_flag(ctx, &header, c->header.close_symbol, + c->header.close_symbol, c->header.align, ZR_WINDOW_HIDDEN); + if (win->flags & ZR_WINDOW_MINIMIZABLE) + zr_header_flag(ctx, &header, c->header.maximize_symbol, + c->header.minimize_symbol, c->header.align, ZR_WINDOW_MINIMIZED); + + { + /* window header title */ + zr_size text_len = zr_strsiz(title); + struct zr_rect label = {0,0,0,0}; + + /* calculate and allocate space from the header */ + zr_size t = c->font.width(c->font.userdata, c->font.height, title, text_len); + if (c->header.align == ZR_HEADER_LEFT) { + header.back = header.back - (3 * item_padding.x + (float)t); + label.x = header.back; + } else { + label.x = header.front; + header.front += 3 * item_padding.x + (float)t; + } + + { + /* calculate label bounds and draw text */ + struct zr_text text; + text.padding = zr_vec2(0,0); + text.background = c->colors[ZR_COLOR_HEADER]; + text.text = c->colors[ZR_COLOR_TEXT]; + + label.y = header.y; + label.h = c->font.height + 2 * item_padding.y; + label.w = MAX((float)t + 2 * item_padding.x, 4 * item_padding.x); + zr_widget_text(out, label,(const char*)title, text_len, &text, + ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, &c->font); + } + } + out->clip = old_clip; + } + + /* fix header height for transistion between minimized and maximized window state */ + if (win->flags & ZR_WINDOW_MINIMIZED && !(layout->flags & ZR_WINDOW_MINIMIZED)) + layout->row.height += 2 * item_spacing.y + 1; + + if (layout->flags & ZR_WINDOW_MINIMIZED) { + /* draw window background if minimized */ + layout->row.height = 0; + zr_draw_rect(out, zr_rect(layout->bounds.x, layout->bounds.y, + layout->bounds.w, layout->row.height), 0, c->colors[ZR_COLOR_WINDOW]); + } else if (!(layout->flags & ZR_WINDOW_DYNAMIC)) { + /* draw static window body */ + struct zr_rect body = layout->bounds; + if (header_active) { + body.y += layout->header_h; + body.h -= layout->header_h; + } + zr_draw_rect(out, body, 0, c->colors[ZR_COLOR_WINDOW]); + } else { + /* draw dynamic window body */ + zr_draw_rect(out, zr_rect(layout->bounds.x, layout->bounds.y, + layout->bounds.w, layout->row.height + window_padding.y), 0, + c->colors[ZR_COLOR_WINDOW]); + } + + /* draw top window border line */ + if (layout->flags & ZR_WINDOW_BORDER) { + zr_draw_line(out, layout->bounds.x, layout->bounds.y, + layout->bounds.x + layout->bounds.w, layout->bounds.y, + c->colors[ZR_COLOR_BORDER]); + } + + { + /* calculate and set the window clipping rectangle*/ + struct zr_rect clip; + if (!(win->flags & ZR_WINDOW_DYNAMIC)) { + layout->clip.x = win->bounds.x + window_padding.x; + layout->clip.w = layout->width - 2 * window_padding.x; + } else { + layout->clip.x = win->bounds.x; + layout->clip.w = layout->width; + } + + layout->clip.h = win->bounds.h - (layout->footer_h + layout->header_h); + layout->clip.h -= (window_padding.y + item_padding.y); + layout->clip.y = win->bounds.y; + if (win->flags & ZR_WINDOW_BORDER) { + layout->clip.y += 1; + layout->clip.h -= 2; + } + + /* combo box and menu do not have header space */ + if (!(win->flags & ZR_WINDOW_COMBO) && !(win->flags & ZR_WINDOW_MENU)) + layout->clip.y += layout->header_h; + + zr_unify(&clip, &win->buffer.clip, layout->clip.x, layout->clip.y, + layout->clip.x + layout->clip.w, layout->clip.y + layout->clip.h); + zr_draw_scissor(out, clip); + + win->buffer.clip.x = layout->bounds.x; + win->buffer.clip.w = layout->width; + if (!(win->flags & ZR_WINDOW_NO_SCROLLBAR)) + win->buffer.clip.w += scrollbar_size; + } + return !(layout->flags & ZR_WINDOW_HIDDEN) && !(layout->flags & ZR_WINDOW_MINIMIZED); +} + +static void +zr_panel_end(struct zr_context *ctx) +{ + /* local read only style variables */ + float scrollbar_size; + struct zr_vec2 item_padding; + struct zr_vec2 item_spacing; + struct zr_vec2 window_padding; + struct zr_vec2 scaler_size; + struct zr_rect footer = {0,0,0,0}; + + /* pointers to subsystems */ + struct zr_input *in; + struct zr_window *window; + struct zr_panel *layout; + struct zr_command_buffer *out; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + window = ctx->current; + layout = window->layout; + config = &ctx->style; + out = &window->buffer; + in = (layout->flags & ZR_WINDOW_ROM) ? 0 :&ctx->input; + if (!(layout->flags & ZR_WINDOW_SUB)) + zr_draw_scissor(out, zr_null_rect); + + /* cache configuration data */ + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + item_spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + window_padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + scrollbar_size = zr_get_property(ctx, ZR_PROPERTY_SCROLLBAR_SIZE).x; + scaler_size = zr_get_property(ctx, ZR_PROPERTY_SCALER_SIZE); + + /* update the current cursor Y-position to point over the last added widget */ + layout->at_y += layout->row.height; + + /* draw footer and fill empty spaces inside a dynamically growing panel */ + if (layout->flags & ZR_WINDOW_DYNAMIC && !(layout->flags & ZR_WINDOW_MINIMIZED)) { + layout->height = layout->at_y - layout->bounds.y; + layout->height = MIN(layout->height, layout->bounds.h); + if ((layout->offset->x == 0) || (layout->flags & ZR_WINDOW_NO_SCROLLBAR)) { + /* special case for dynamic windows without horizontal scrollbar + * or hidden scrollbars */ + footer.x = window->bounds.x; + footer.y = window->bounds.y + layout->height + item_spacing.y; + footer.w = window->bounds.w + scrollbar_size; + layout->footer_h = 0; + footer.h = 0; + + if ((layout->offset->x == 0) && !(layout->flags & ZR_WINDOW_NO_SCROLLBAR)) { + /* special case for windows like combobox, menu require draw call + * to fill the empty scrollbar background */ + struct zr_rect bounds; + bounds.x = layout->bounds.x + layout->width; + bounds.y = layout->clip.y; + bounds.w = scrollbar_size; + bounds.h = layout->height; + zr_draw_rect(out, bounds, 0, config->colors[ZR_COLOR_WINDOW]); + } + } else { + /* dynamic window with visible scrollbars and therefore bigger footer */ + footer.x = window->bounds.x; + footer.w = window->bounds.w + scrollbar_size; + footer.h = layout->footer_h; + if ((layout->flags & ZR_WINDOW_COMBO) || (layout->flags & ZR_WINDOW_MENU) || + (layout->flags & ZR_WINDOW_CONTEXTUAL)) + footer.y = window->bounds.y + layout->height; + else footer.y = window->bounds.y + layout->height + layout->footer_h; + zr_draw_rect(out, footer, 0, config->colors[ZR_COLOR_WINDOW]); + + if (!(layout->flags & ZR_WINDOW_COMBO) && !(layout->flags & ZR_WINDOW_MENU)) { + /* fill empty scrollbar space */ + struct zr_rect bounds; + bounds.x = layout->bounds.x; + bounds.y = window->bounds.y + layout->height; + bounds.w = layout->bounds.w; + bounds.h = layout->row.height; + zr_draw_rect(out, bounds, 0, config->colors[ZR_COLOR_WINDOW]); + } + } + } + + /* scrollbars */ + if (!(layout->flags & ZR_WINDOW_NO_SCROLLBAR) && !(layout->flags & ZR_WINDOW_MINIMIZED)) { + struct zr_rect bounds; + float scroll_target, scroll_offset, scroll_step; + + /* fill scrollbar style */ + struct zr_scrollbar scroll; + scroll.rounding = config->rounding[ZR_ROUNDING_SCROLLBAR]; + scroll.background = config->colors[ZR_COLOR_SCROLLBAR]; + scroll.normal = config->colors[ZR_COLOR_SCROLLBAR_CURSOR]; + scroll.hover = config->colors[ZR_COLOR_SCROLLBAR_CURSOR_HOVER]; + scroll.active = config->colors[ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE]; + scroll.border = config->colors[ZR_COLOR_BORDER]; + { + /* vertical scollbar */ + enum zr_widget_status state; + bounds.x = layout->bounds.x + layout->width; + bounds.y = layout->clip.y; + bounds.w = scrollbar_size; + bounds.h = layout->clip.h; + if (layout->flags & ZR_WINDOW_BORDER) bounds.h -= 1; + + scroll_offset = layout->offset->y; + scroll_step = layout->clip.h * 0.10f; + scroll_target = (float)(int)(layout->at_y - layout->clip.y); + scroll.has_scrolling = (window == ctx->active); + scroll_offset = zr_do_scrollbarv(&state, out, bounds, scroll_offset, + scroll_target, scroll_step, &scroll, in); + layout->offset->y = (unsigned short)scroll_offset; + } + { + /* horizontal scrollbar */ + enum zr_widget_status state; + bounds.x = layout->bounds.x + window_padding.x; + if (layout->flags & ZR_WINDOW_SUB) { + bounds.h = scrollbar_size; + bounds.y = (layout->flags & ZR_WINDOW_BORDER) ? + layout->bounds.y + 1 : layout->bounds.y; + bounds.y += layout->header_h + layout->menu.h + layout->height; + bounds.w = layout->clip.w; + } else if (layout->flags & ZR_WINDOW_DYNAMIC) { + bounds.h = MIN(scrollbar_size, layout->footer_h); + bounds.w = layout->bounds.w; + bounds.y = footer.y; + } else { + bounds.h = MIN(scrollbar_size, layout->footer_h); + bounds.y = layout->bounds.y + window->bounds.h; + bounds.y -= MAX(layout->footer_h, scrollbar_size); + bounds.w = layout->width - 2 * window_padding.x; + } + scroll_offset = layout->offset->x; + scroll_target = (float)(int)(layout->max_x - bounds.x); + scroll_step = layout->max_x * 0.05f; + scroll.has_scrolling = zr_false; + scroll_offset = zr_do_scrollbarh(&state, out, bounds, scroll_offset, + scroll_target, scroll_step, &scroll, in); + layout->offset->x = (unsigned short)scroll_offset; + } + } + + /* draw the panel scaler into the right corner of the panel footer and + * update panel size if user drags the scaler */ + if ((layout->flags & ZR_WINDOW_SCALABLE) && in && !(layout->flags & ZR_WINDOW_MINIMIZED)) { + struct zr_color col = config->colors[ZR_COLOR_SCALER]; + float scaler_w = MAX(0, scaler_size.x - item_padding.x); + float scaler_h = MAX(0, scaler_size.y - item_padding.y); + float scaler_x = (layout->bounds.x + layout->bounds.w) - (item_padding.x + scaler_w); + + float scaler_y; + if (layout->flags & ZR_WINDOW_DYNAMIC) + scaler_y = footer.y + layout->footer_h - scaler_size.y; + else scaler_y = layout->bounds.y + layout->bounds.h - scaler_size.y; + zr_draw_triangle(out, scaler_x + scaler_w, scaler_y, + scaler_x + scaler_w, scaler_y + scaler_h, scaler_x, scaler_y + scaler_h, col); + + if (!(window->flags & ZR_WINDOW_ROM)) { + float prev_x = in->mouse.prev.x; + float prev_y = in->mouse.prev.y; + struct zr_vec2 window_size = zr_get_property(ctx, ZR_PROPERTY_SIZE); + int incursor = ZR_INBOX(prev_x,prev_y,scaler_x,scaler_y,scaler_w,scaler_h); + + if (zr_input_is_mouse_down(in, ZR_BUTTON_LEFT) && incursor) { + window->bounds.w = MAX(window_size.x, window->bounds.w + in->mouse.delta.x); + /* draging in y-direction is only possible if static window */ + if (!(layout->flags & ZR_WINDOW_DYNAMIC)) + window->bounds.h = MAX(window_size.y, window->bounds.h + in->mouse.delta.y); + } + } + } + + if (layout->flags & ZR_WINDOW_BORDER) { + /* draw the border around the complete panel */ + const float width = (layout->flags & ZR_WINDOW_NO_SCROLLBAR) ? + layout->width: layout->width + scrollbar_size; + const float padding_y = (layout->flags & ZR_WINDOW_MINIMIZED) ? + window->bounds.y + layout->header_h: + (layout->flags & ZR_WINDOW_DYNAMIC)? + layout->footer_h + footer.y: + layout->bounds.y + layout->bounds.h; + + if (window->flags & ZR_WINDOW_BORDER_HEADER) + zr_draw_line(out, window->bounds.x, window->bounds.y + layout->header_h, + window->bounds.x + window->bounds.w, window->bounds.y + layout->header_h, + config->colors[ZR_COLOR_BORDER]); + zr_draw_line(out, window->bounds.x, padding_y, window->bounds.x + width, + padding_y, config->colors[ZR_COLOR_BORDER]); + zr_draw_line(out, window->bounds.x, window->bounds.y, window->bounds.x, + padding_y, config->colors[ZR_COLOR_BORDER]); + zr_draw_line(out, window->bounds.x + width, window->bounds.y, + window->bounds.x + width, padding_y, config->colors[ZR_COLOR_BORDER]); + } + + if (!(window->flags & ZR_WINDOW_SUB)) { + /* window is hidden so clear command buffer */ + if (layout->flags & ZR_WINDOW_HIDDEN) + zr_command_buffer_reset(&window->buffer); + /* window is visible and not tab */ + else zr_finish(ctx, window); + } + + /* ZR_WINDOW_REMOVE_ROM flag was set so remove ZR_WINDOW_ROM */ + if (layout->flags & ZR_WINDOW_REMOVE_ROM) { + layout->flags &= ~(zr_flags)ZR_WINDOW_ROM; + layout->flags &= ~(zr_flags)ZR_WINDOW_REMOVE_ROM; + } + window->flags = layout->flags; + + /* property garbage collector */ + if (window->property.active && window->property.old != window->property.seq && + window->property.active == window->property.prev) { + zr_zero(&window->property, sizeof(window->property)); + } else { + window->property.old = window->property.seq; + window->property.prev = window->property.active; + window->property.seq = 0; + } + + /* edit garbage collector */ + if (window->edit.active && window->edit.old != window->edit.seq && + window->edit.active == window->edit.prev) { + zr_zero(&window->edit, sizeof(window->edit)); + } else { + window->edit.old = window->edit.seq; + window->edit.prev = window->edit.active; + window->edit.seq = 0; + } + + /* contextual gargabe collector */ + if (window->popup.active_con && window->popup.con_old != window->popup.con_count) { + window->popup.con_count = 0; + window->popup.con_old = 0; + window->popup.active_con = 0; + } else { + window->popup.con_old = window->popup.con_count; + window->popup.con_count = 0; + } + window->popup.combo_count = 0; + /* helper to make sure you have a 'zr_layout_push' + * for every 'zr_layout_pop' */ + ZR_ASSERT(!layout->row.tree_depth); +} + +void +zr_menubar_begin(struct zr_context *ctx) +{ + struct zr_panel *layout; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + layout = ctx->current->layout; + if (layout->flags & ZR_WINDOW_HIDDEN || layout->flags & ZR_WINDOW_MINIMIZED) + return; + + layout->menu.x = layout->at_x; + layout->menu.y = layout->bounds.y + layout->header_h; + layout->menu.w = layout->width; + layout->menu.offset = *layout->offset; + layout->offset->y = 0; +} + +void +zr_menubar_end(struct zr_context *ctx) +{ + struct zr_window *win; + struct zr_panel *layout; + struct zr_command_buffer *out; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + if (!ctx || layout->flags & ZR_WINDOW_HIDDEN || layout->flags & ZR_WINDOW_MINIMIZED) + return; + + out = &win->buffer; + layout->menu.h = layout->at_y - layout->menu.y; + layout->clip.y = layout->bounds.y + layout->header_h + layout->menu.h + layout->row.height; + layout->height -= layout->menu.h; + *layout->offset = layout->menu.offset; + layout->clip.h -= layout->menu.h + layout->row.height; + layout->at_y = layout->menu.y + layout->menu.h; + zr_draw_scissor(out, layout->clip); +} +/* ------------------------------------------------------------- + * + * LAYOUT + * + * --------------------------------------------------------------*/ +static void +zr_panel_layout(const struct zr_context *ctx, struct zr_window *win, + float height, zr_size cols) +{ + const struct zr_style *config; + const struct zr_color *color; + struct zr_command_buffer *out; + struct zr_vec2 item_spacing; + struct zr_vec2 panel_padding; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + /* prefetch some configuration data */ + layout = win->layout; + config = &ctx->style; + out = &win->buffer; + color = &config->colors[ZR_COLOR_WINDOW]; + item_spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + panel_padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + + /* update the current row and set the current row layout */ + layout->row.index = 0; + layout->at_y += layout->row.height; + layout->row.columns = cols; + layout->row.height = height + item_spacing.y; + layout->row.item_offset = 0; + if (layout->flags & ZR_WINDOW_DYNAMIC) + zr_draw_rect(out, zr_rect(layout->bounds.x, layout->at_y, + layout->bounds.w, height + panel_padding.y), 0, *color); +} + +static void +zr_row_layout(struct zr_context *ctx, enum zr_layout_format fmt, + float height, zr_size cols, zr_size width) +{ + /* update the current row and set the current row layout */ + struct zr_window *win; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + zr_panel_layout(ctx, win, height, cols); + if (fmt == ZR_DYNAMIC) + win->layout->row.type = ZR_LAYOUT_DYNAMIC_FIXED; + else win->layout->row.type = ZR_LAYOUT_STATIC_FIXED; + + win->layout->row.item_width = (float)width; + win->layout->row.ratio = 0; + win->layout->row.item_offset = 0; + win->layout->row.filled = 0; +} + +void +zr_layout_row_dynamic(struct zr_context *ctx, float height, zr_size cols) +{zr_row_layout(ctx, ZR_DYNAMIC, height, cols, 0);} + +void +zr_layout_row_static(struct zr_context *ctx, float height, + zr_size item_width, zr_size cols) +{zr_row_layout(ctx, ZR_STATIC, height, cols, item_width);} + +void +zr_layout_row_begin(struct zr_context *ctx, + enum zr_layout_format fmt, float row_height, zr_size cols) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + + zr_panel_layout(ctx, win, row_height, cols); + if (fmt == ZR_DYNAMIC) + layout->row.type = ZR_LAYOUT_DYNAMIC_ROW; + else layout->row.type = ZR_LAYOUT_STATIC_ROW; + + layout->row.ratio = 0; + layout->row.item_width = 0; + layout->row.item_offset = 0; + layout->row.filled = 0; + layout->row.columns = cols; +} + +void +zr_layout_row_push(struct zr_context *ctx, float ratio_or_width) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + + if (layout->row.type == ZR_LAYOUT_DYNAMIC_ROW) { + float ratio = ratio_or_width; + if ((ratio + layout->row.filled) > 1.0f) return; + if (ratio > 0.0f) + layout->row.item_width = ZR_SATURATE(ratio); + else layout->row.item_width = 1.0f - layout->row.filled; + } else layout->row.item_width = ratio_or_width; +} + +void +zr_layout_row_end(struct zr_context *ctx) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.item_width = 0; + layout->row.item_offset = 0; +} + +void +zr_layout_row(struct zr_context *ctx, enum zr_layout_format fmt, + float height, zr_size cols, const float *ratio) +{ + zr_size i; + zr_size n_undef = 0; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + zr_panel_layout(ctx, win, height, cols); + if (fmt == ZR_DYNAMIC) { + /* calculate width of undefined widget ratios */ + float r = 0; + layout->row.ratio = ratio; + for (i = 0; i < cols; ++i) { + if (ratio[i] < 0.0f) + n_undef++; + else r += ratio[i]; + } + r = ZR_SATURATE(1.0f - r); + layout->row.type = ZR_LAYOUT_DYNAMIC; + layout->row.item_width = (r > 0 && n_undef > 0) ? (r / (float)n_undef):0; + } else { + layout->row.ratio = ratio; + layout->row.type = ZR_LAYOUT_STATIC; + layout->row.item_width = 0; + layout->row.item_offset = 0; + } + layout->row.item_offset = 0; + layout->row.filled = 0; +} + +void +zr_layout_space_begin(struct zr_context *ctx, + enum zr_layout_format fmt, float height, zr_size widget_count) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + zr_panel_layout(ctx, win, height, widget_count); + if (fmt == ZR_STATIC) + layout->row.type = ZR_LAYOUT_STATIC_FREE; + else layout->row.type = ZR_LAYOUT_DYNAMIC_FREE; + + layout->row.ratio = 0; + layout->row.item_width = 0; + layout->row.item_offset = 0; + layout->row.filled = 0; +} + +void +zr_layout_space_end(struct zr_context *ctx) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.item_width = 0; + layout->row.item_height = 0; + layout->row.item_offset = 0; + zr_zero(&layout->row.item, sizeof(layout->row.item)); +} + +void +zr_layout_space_push(struct zr_context *ctx, struct zr_rect rect) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + layout->row.item = rect; +} + +struct zr_rect +zr_layout_space_bounds(struct zr_context *ctx) +{ + struct zr_rect ret; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x = layout->clip.x; + ret.y = layout->clip.y; + ret.w = layout->clip.w; + ret.h = layout->row.height; + return ret; +} + +struct zr_vec2 +zr_layout_space_to_screen(struct zr_context *ctx, struct zr_vec2 ret) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += layout->at_x - layout->offset->x; + ret.y += layout->at_y - layout->offset->y; + return ret; +} + +struct zr_vec2 +zr_layout_space_to_local(struct zr_context *ctx, struct zr_vec2 ret) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += -layout->at_x + layout->offset->x; + ret.y += -layout->at_y + layout->offset->y; + return ret; +} + +struct zr_rect +zr_layout_space_rect_to_screen(struct zr_context *ctx, struct zr_rect ret) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += layout->at_x - layout->offset->x; + ret.y += layout->at_y - layout->offset->y; + return ret; +} + +struct zr_rect +zr_layout_space_rect_to_local(struct zr_context *ctx, struct zr_rect ret) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + win = ctx->current; + layout = win->layout; + + ret.x += -layout->at_x + layout->offset->x; + ret.y += -layout->at_y + layout->offset->y; + return ret; +} + +static void +zr_panel_alloc_row(const struct zr_context *ctx, struct zr_window *win) +{ + struct zr_panel *layout = win->layout; + struct zr_vec2 spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + const float row_height = layout->row.height - spacing.y; + zr_panel_layout(ctx, win, row_height, layout->row.columns); +} + +static void +zr_layout_widget_space(struct zr_rect *bounds, const struct zr_context *ctx, + struct zr_window *win, int modify) +{ + float panel_padding, panel_spacing, panel_space; + float item_offset = 0, item_width = 0, item_spacing = 0; + struct zr_vec2 spacing, padding; + + struct zr_panel *layout; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + ZR_ASSERT(bounds); + + /* cache some configuration data */ + spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + + /* calculate the useable panel space */ + panel_padding = 2 * padding.x; + panel_spacing = (float)(layout->row.columns - 1) * spacing.x; + panel_space = layout->width - panel_padding - panel_spacing; + + /* calculate the width of one item inside the current layout space */ + switch (layout->row.type) { + case ZR_LAYOUT_DYNAMIC_FIXED: { + /* scaling fixed size widgets item width */ + item_width = panel_space / (float)layout->row.columns; + item_offset = (float)layout->row.index * item_width; + item_spacing = (float)layout->row.index * spacing.x; + } break; + case ZR_LAYOUT_DYNAMIC_ROW: { + /* scaling single ratio widget width */ + item_width = layout->row.item_width * panel_space; + item_offset = layout->row.item_offset; + item_spacing = (float)layout->row.index * spacing.x; + + if (modify) { + layout->row.item_offset += item_width + spacing.x; + layout->row.filled += layout->row.item_width; + layout->row.index = 0; + } + } break; + case ZR_LAYOUT_DYNAMIC_FREE: { + /* panel width depended free widget placing */ + bounds->x = layout->at_x + (layout->width * layout->row.item.x); + bounds->x -= layout->offset->x; + bounds->y = layout->at_y + (layout->row.height * layout->row.item.y); + bounds->y -= layout->offset->y; + bounds->w = layout->width * layout->row.item.w; + bounds->h = layout->row.height * layout->row.item.h; + return; + }; + case ZR_LAYOUT_DYNAMIC: { + /* scaling arrays of panel width ratios for every widget */ + float ratio; + ZR_ASSERT(layout->row.ratio); + ratio = (layout->row.ratio[layout->row.index] < 0) ? + layout->row.item_width : layout->row.ratio[layout->row.index]; + + item_spacing = (float)layout->row.index * spacing.x; + if (layout->row.index < layout->row.columns-1) + item_width = (ratio * panel_space) - spacing.x; + else item_width = (ratio * panel_space); + + item_offset = layout->row.item_offset; + if (modify) { + layout->row.item_offset += item_width + spacing.x; + layout->row.filled += ratio; + } + } break; + case ZR_LAYOUT_STATIC_FIXED: { + /* non-scaling fixed widgets item width */ + item_width = layout->row.item_width; + item_offset = (float)layout->row.index * item_width; + item_spacing = (float)layout->row.index * spacing.x; + } break; + case ZR_LAYOUT_STATIC_ROW: { + /* scaling single ratio widget width */ + item_width = layout->row.item_width; + item_offset = layout->row.item_offset; + item_spacing = (float)layout->row.index * spacing.x; + if (modify) { + layout->row.item_offset += item_width + spacing.x; + layout->row.index = 0; + } + } break; + case ZR_LAYOUT_STATIC_FREE: { + /* free widget placing */ + bounds->x = layout->at_x + layout->row.item.x; + bounds->w = layout->row.item.w; + if (((bounds->x + bounds->w) > layout->max_x) && modify) + layout->max_x = (bounds->x + bounds->w); + bounds->x -= layout->offset->x; + bounds->y = layout->at_y + layout->row.item.y; + bounds->y -= layout->offset->y; + bounds->h = layout->row.item.h; + return; + }; + case ZR_LAYOUT_STATIC: { + /* non-scaling array of panel pixel width for every widget */ + item_spacing = (float)layout->row.index * spacing.x; + item_width = layout->row.ratio[layout->row.index]; + item_offset = layout->row.item_offset; + if (modify) layout->row.item_offset += item_width + spacing.x; + } break; + default: ZR_ASSERT(0); break; + }; + + /* set the bounds of the newly allocated widget */ + bounds->w = item_width; + bounds->h = layout->row.height - spacing.y; + bounds->y = layout->at_y - layout->offset->y; + bounds->x = layout->at_x + item_offset + item_spacing + padding.x; + if (((bounds->x + bounds->w) > layout->max_x) && modify) + layout->max_x = bounds->x + bounds->w; + bounds->x -= layout->offset->x; +} + +static void +zr_panel_alloc_space(struct zr_rect *bounds, const struct zr_context *ctx) +{ + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + /* check if the end of the row has been hit and begin new row if so */ + win = ctx->current; + layout = win->layout; + if (layout->row.index >= layout->row.columns) + zr_panel_alloc_row(ctx, win); + + /* calculate widget position and size */ + zr_layout_widget_space(bounds, ctx, win, zr_true); + layout->row.index++; +} + +void +zr_layout_peek(struct zr_rect *bounds, struct zr_context *ctx) +{ + float y; + zr_size index; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + y = layout->at_y; + index = layout->row.index; + if (layout->row.index >= layout->row.columns) { + layout->at_y += layout->row.height; + layout->row.index = 0; + } + zr_layout_widget_space(bounds, ctx, win, zr_false); + layout->at_y = y; + layout->row.index = index; +} + +int +zr_layout_push(struct zr_context *ctx, enum zr_layout_node_type type, + const char *title, enum zr_collapse_states initial_state) +{ + struct zr_window *win; + struct zr_panel *layout; + const struct zr_style *config; + struct zr_command_buffer *out; + const struct zr_input *input; + + struct zr_vec2 item_spacing; + struct zr_vec2 item_padding; + struct zr_vec2 panel_padding; + struct zr_rect header = {0,0,0,0}; + struct zr_rect sym = {0,0,0,0}; + + enum zr_widget_status ws; + enum zr_widget_state widget_state; + + zr_hash title_hash; + int title_len; + zr_uint *state = 0; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + /* cache some data */ + win = ctx->current; + layout = win->layout; + out = &win->buffer; + config = &ctx->style; + + item_spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + panel_padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + + /* calculate header bounds and draw background */ + zr_layout_row_dynamic(ctx, config->font.height + 2 * item_padding.y, 1); + widget_state = zr_widget(&header, ctx); + if (type == ZR_LAYOUT_TAB) + zr_draw_rect(out, header, 0, config->colors[ZR_COLOR_TAB_HEADER]); + + /* find or create tab persistent state (open/closed) */ + title_len = (int)zr_strsiz(title); + title_hash = zr_murmur_hash(title, (int)title_len, ZR_WINDOW_HIDDEN); + state = zr_find_value(win, title_hash); + if (!state) { + state = zr_add_value(ctx, win, title_hash, 0); + *state = initial_state; + } + + /* update node state */ + input = (!(layout->flags & ZR_WINDOW_ROM)) ? &ctx->input: 0; + input = (input && widget_state == ZR_WIDGET_VALID) ? &ctx->input : 0; + if (zr_button_behavior(&ws, header, input, ZR_BUTTON_DEFAULT)) + *state = (*state == ZR_MAXIMIZED) ? ZR_MINIMIZED : ZR_MAXIMIZED; + + { + /* and draw closing/open icon */ + enum zr_heading heading; + struct zr_vec2 points[3]; + heading = (*state == ZR_MAXIMIZED) ? ZR_DOWN : ZR_RIGHT; + + /* calculate the triangle bounds */ + sym.w = sym.h = config->font.height; + sym.y = header.y + item_padding.y; + sym.x = header.x + panel_padding.x + item_padding.x; + + /* calculate the triangle points and draw triangle */ + zr_triangle_from_direction(points, sym, 0, 0, heading); + zr_draw_triangle(&win->buffer, points[0].x, points[0].y, + points[1].x, points[1].y, points[2].x, points[2].y, config->colors[ZR_COLOR_TEXT]); + + /* calculate the space the icon occupied */ + sym.w = config->font.height + 2 * item_padding.x; + } + { + /* draw node label */ + struct zr_color color; + struct zr_rect label; + struct zr_text text; + + header.w = MAX(header.w, sym.w + item_spacing.y + panel_padding.x); + label.x = sym.x + sym.w + item_spacing.x; + label.y = sym.y; + label.w = header.w - (sym.w + item_spacing.y + panel_padding.x); + label.h = config->font.height; + + color = (type == ZR_LAYOUT_TAB) ? + config->colors[ZR_COLOR_TAB_HEADER]: + config->colors[ZR_COLOR_WINDOW]; + text.padding = zr_vec2(0,0); + text.background = color; + text.text = config->colors[ZR_COLOR_TEXT]; + zr_widget_text(out, label, title, zr_strsiz(title), &text, + ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, &config->font); + } + + if (type == ZR_LAYOUT_TAB) { + /* special node with border around the header */ + zr_draw_line(out, header.x, header.y, + header.x + header.w-1, header.y, config->colors[ZR_COLOR_BORDER]); + zr_draw_line(out, header.x, header.y, + header.x, header.y + header.h, config->colors[ZR_COLOR_BORDER]); + zr_draw_line(out, header.x + header.w-1, header.y, + header.x + header.w-1, header.y + header.h, config->colors[ZR_COLOR_BORDER]); + zr_draw_line(out, header.x, header.y + header.h, + header.x + header.w-1, header.y + header.h, config->colors[ZR_COLOR_BORDER]); + } + + if (*state == ZR_MAXIMIZED) { + layout->at_x = header.x + layout->offset->x; + layout->width = MAX(layout->width, 2 * panel_padding.x); + layout->width -= 2 * panel_padding.x; + layout->row.tree_depth++; + return zr_true; + } else return zr_false; +} + +void +zr_layout_pop(struct zr_context *ctx) +{ + struct zr_vec2 panel_padding; + struct zr_window *win = 0; + struct zr_panel *layout = 0; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + panel_padding = zr_get_property(ctx, ZR_PROPERTY_PADDING); + layout->at_x -= panel_padding.x; + layout->width += 2 * panel_padding.x; + ZR_ASSERT(layout->row.tree_depth); + layout->row.tree_depth--; +} +/*---------------------------------------------------------------- + * + * WIDGETS + * + * --------------------------------------------------------------*/ +void +zr_spacing(struct zr_context *ctx, zr_size cols) +{ + zr_size i, index, rows; + struct zr_rect nil; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + /* spacing over row boundries */ + win = ctx->current; + layout = win->layout; + index = (layout->row.index + cols) % layout->row.columns; + rows = (layout->row.index + cols) / layout->row.columns; + if (rows) { + for (i = 0; i < rows; ++i) + zr_panel_alloc_row(ctx, win); + cols = index; + } + + /* non table layout need to allocate space */ + if (layout->row.type != ZR_LAYOUT_DYNAMIC_FIXED && + layout->row.type != ZR_LAYOUT_STATIC_FIXED) { + for (i = 0; i < cols; ++i) + zr_panel_alloc_space(&nil, ctx); + } + layout->row.index = index; +} + +void +zr_seperator(struct zr_context *ctx) +{ + struct zr_vec2 item_padding; + struct zr_vec2 item_spacing; + struct zr_rect bounds; + + struct zr_window *win; + struct zr_panel *layout; + struct zr_command_buffer *out; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + out = &win->buffer; + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + item_spacing = zr_get_property(ctx, ZR_PROPERTY_ITEM_SPACING); + + bounds.h = 1; + bounds.w = MAX(layout->width, 2 * item_spacing.x + 2 * item_padding.x); + bounds.y = (layout->at_y + layout->row.height + item_padding.y) - layout->offset->y; + bounds.x = layout->at_x + item_spacing.x + item_padding.x - layout->offset->x; + bounds.w = bounds.w - (2 * item_spacing.x + 2 * item_padding.x); + zr_draw_line(out, bounds.x, bounds.y, bounds.x + bounds.w, + bounds.y + bounds.h, config->colors[ZR_COLOR_BORDER]); +} + +enum zr_widget_state +zr_widget(struct zr_rect *bounds, const struct zr_context *ctx) +{ + struct zr_rect *c = 0; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return ZR_WIDGET_INVALID; + + /* allocate space and check if the widget needs to be updated and drawn */ + win = ctx->current; + layout = win->layout; + zr_panel_alloc_space(bounds, ctx); + c = &layout->clip; + if (!ZR_INTERSECT(c->x, c->y, c->w, c->h, bounds->x, bounds->y, bounds->w, bounds->h)) + return ZR_WIDGET_INVALID; + if (!ZR_CONTAINS(bounds->x, bounds->y, bounds->w, bounds->h, c->x, c->y, c->w, c->h)) + return ZR_WIDGET_ROM; + return ZR_WIDGET_VALID; +} + +enum zr_widget_state +zr_widget_fitting(struct zr_rect *bounds, struct zr_context *ctx) +{ + /* update the bounds to stand without padding */ + enum zr_widget_state state; + struct zr_window *win; + struct zr_panel *layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return ZR_WIDGET_INVALID; + + win = ctx->current; + layout = win->layout; + state = zr_widget(bounds, ctx); + if (layout->row.index == 1) { + bounds->w += ctx->style.properties[ZR_PROPERTY_PADDING].x; + bounds->x -= ctx->style.properties[ZR_PROPERTY_PADDING].x; + } else bounds->x -= ctx->style.properties[ZR_PROPERTY_ITEM_PADDING].x; + if (layout->row.index == layout->row.columns) + bounds->w += ctx->style.properties[ZR_PROPERTY_PADDING].x; + else bounds->w += ctx->style.properties[ZR_PROPERTY_ITEM_PADDING].x; + return state; +} + +void +zr_text_colored(struct zr_context *ctx, const char *str, zr_size len, + zr_flags alignment, struct zr_color color) +{ + struct zr_rect bounds; + struct zr_text text; + struct zr_vec2 item_padding; + + struct zr_window *win; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + zr_panel_alloc_space(&bounds, ctx); + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + + text.padding.x = item_padding.x; + text.padding.y = item_padding.y; + text.background = config->colors[ZR_COLOR_WINDOW]; + text.text = color; + zr_widget_text(&win->buffer, bounds, str, len, &text, alignment, &config->font); +} + +void +zr_text_wrap_colored(struct zr_context *ctx, const char *str, + zr_size len, struct zr_color color) +{ + struct zr_vec2 item_padding; + struct zr_rect bounds; + struct zr_text text; + + struct zr_window *win; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + zr_panel_alloc_space(&bounds, ctx); + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + + text.padding.x = item_padding.x; + text.padding.y = item_padding.y; + text.background = config->colors[ZR_COLOR_WINDOW]; + text.text = color; + zr_widget_text_wrap(&win->buffer, bounds, str, len, &text, &config->font); +} + +void +zr_text_wrap(struct zr_context *ctx, const char *str, zr_size len) +{zr_text_wrap_colored(ctx, str, len, ctx->style.colors[ZR_COLOR_TEXT]);} + +void +zr_text(struct zr_context *ctx, const char *str, zr_size len, + zr_flags alignment) +{zr_text_colored(ctx, str, len, alignment, ctx->style.colors[ZR_COLOR_TEXT]);} + +void +zr_label_colored(struct zr_context *ctx, const char *text, + zr_flags align, struct zr_color color) +{zr_text_colored(ctx, text, zr_strsiz(text), align, color);} + +void +zr_label(struct zr_context *ctx, const char *text, zr_flags align) +{zr_text(ctx, text, zr_strsiz(text), align);} + +void +zr_label_wrap(struct zr_context *ctx, const char *str) +{zr_text_wrap(ctx, str, zr_strsiz(str));} + +void +zr_label_colored_wrap(struct zr_context *ctx, const char *str, struct zr_color color) +{zr_text_wrap_colored(ctx, str, zr_strsiz(str), color);} + +void +zr_image(struct zr_context *ctx, struct zr_image img) +{ + struct zr_vec2 item_padding; + struct zr_rect bounds; + struct zr_window *win; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + if (!zr_widget(&bounds, ctx)) + return; + + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + bounds.x += item_padding.x; + bounds.y += item_padding.y; + bounds.w -= 2 * item_padding.x; + bounds.h -= 2 * item_padding.y; + zr_draw_image(&win->buffer, bounds, &img); +} + +enum zr_button_alloc {ZR_BUTTON_NORMAL, ZR_BUTTON_FITTING}; +static enum zr_widget_state +zr_button(struct zr_button *button, struct zr_rect *bounds, + struct zr_context *ctx, enum zr_button_alloc type) +{ + enum zr_widget_state state; + struct zr_vec2 item_padding; + const struct zr_style *config = &ctx->style; + if (type == ZR_BUTTON_NORMAL) + state = zr_widget(bounds, ctx); + else state = zr_widget_fitting(bounds, ctx); + if (!state) return state; + + zr_zero(button, sizeof(*button)); + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + button->touch_pad = zr_get_property(ctx, ZR_PROPERTY_TOUCH_PADDING); + button->rounding = config->rounding[ZR_ROUNDING_BUTTON]; + button->normal = config->colors[ZR_COLOR_BUTTON]; + button->hover = config->colors[ZR_COLOR_BUTTON_HOVER]; + button->active = config->colors[ZR_COLOR_BUTTON_ACTIVE]; + button->border = config->colors[ZR_COLOR_BORDER]; + button->padding.x = item_padding.x; + button->padding.y = item_padding.y; + button->border_width = 1; + return state; +} + +int +zr_button_text(struct zr_context *ctx, const char *str, + enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_text button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + win = ctx->current; + layout = win->layout; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_NORMAL); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + button.alignment = ZR_TEXT_CENTERED|ZR_TEXT_MIDDLE; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + return zr_do_button_text(&ws, &win->buffer, bounds, str, behavior, + &button, i, &config->font); +} + +int +zr_button_color(struct zr_context *ctx, + struct zr_color color, enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + win = ctx->current; + layout = win->layout; + state = zr_button(&button, &bounds, ctx, ZR_BUTTON_NORMAL); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + button.normal = color; + button.hover = color; + button.active = color; + return zr_do_button(&ws, &win->buffer, bounds, &button, i, behavior, &bounds); +} + +int +zr_button_symbol(struct zr_context *ctx, enum zr_symbol_type symbol, + enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_symbol button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + win = ctx->current; + layout = win->layout; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_NORMAL); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + return zr_do_button_symbol(&ws, &win->buffer, bounds, symbol, + behavior, &button, i, &config->font); +} + +int +zr_button_image(struct zr_context *ctx, struct zr_image image, + enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_icon button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + win = ctx->current; + layout = win->layout; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_NORMAL); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + button.padding = zr_vec2(0,0); + return zr_do_button_image(&ws, &win->buffer, bounds, image, behavior, &button, i); +} + +int +zr_button_text_symbol(struct zr_context *ctx, enum zr_symbol_type symbol, + const char *text, zr_flags align, enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_text button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return zr_false; + + win = ctx->current; + layout = win->layout; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_NORMAL); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + button.alignment = ZR_TEXT_CENTERED|ZR_TEXT_MIDDLE; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + return zr_do_button_text_symbol(&ws, &win->buffer, bounds, symbol, text, align, + behavior, &button, &config->font, i); +} + +int +zr_button_text_image(struct zr_context *ctx, struct zr_image img, + const char *text, zr_flags align, enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_text button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return zr_false; + + win = ctx->current; + layout = win->layout; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_NORMAL); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + button.alignment = ZR_TEXT_CENTERED|ZR_TEXT_MIDDLE; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + return zr_do_button_text_image(&ws, &win->buffer, bounds, img, text, align, + behavior, &button, &config->font, i); +} + +int +zr_select(struct zr_context *ctx, const char *str, + zr_flags align, int value) +{ + struct zr_rect bounds; + struct zr_text text; + const struct zr_style *config; + struct zr_vec2 item_padding; + struct zr_color background; + + struct zr_window *win; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return value; + + win = ctx->current; + zr_panel_alloc_space(&bounds, ctx); + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + + background = (!value) ? config->colors[ZR_COLOR_WINDOW]: + config->colors[ZR_COLOR_SELECTABLE]; + if (zr_input_is_mouse_click_in_rect(&ctx->input, ZR_BUTTON_LEFT, bounds)) { + background = config->colors[ZR_COLOR_SELECTABLE_HOVER]; + if (zr_input_has_mouse_click_in_rect(&ctx->input, ZR_BUTTON_LEFT, bounds)) { + if (!zr_input_is_mouse_down(&ctx->input, ZR_BUTTON_LEFT)) + value = !value; + } + } + + text.padding.x = item_padding.x; + text.padding.y = item_padding.y; + text.background = background; + text.text = (!value) ? config->colors[ZR_COLOR_TEXT] : + config->colors[ZR_COLOR_SELECTABLE_TEXT]; + + zr_draw_rect(&win->buffer, bounds, 0, background); + zr_widget_text(&win->buffer, bounds, str, zr_strsiz(str), + &text, align|ZR_TEXT_MIDDLE, &config->font); + return value; +} + +int +zr_selectable(struct zr_context *ctx, const char *str, + zr_flags align, int *value) +{ + int old = *value; + int ret = zr_select(ctx, str, align, old); + *value = ret; + return ret != old; +} + +static enum zr_widget_state +zr_toggle_base(struct zr_toggle *toggle, struct zr_rect *bounds, + const struct zr_context *ctx) +{ + const struct zr_style *config; + struct zr_vec2 item_padding; + enum zr_widget_state state; + state = zr_widget(bounds, ctx); + if (!state) return state; + + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + toggle->rounding = 0; + toggle->padding.x = item_padding.x; + toggle->padding.y = item_padding.y; + toggle->font = config->colors[ZR_COLOR_TEXT]; + toggle->font_background = config->colors[ZR_COLOR_WINDOW]; + return state; +} + +int +zr_check(struct zr_context *ctx, const char *text, int active) +{ + zr_checkbox(ctx, text, &active); + return active; +} + +int +zr_checkbox(struct zr_context *ctx, const char *text, int *is_active) +{ + int old; + struct zr_rect bounds; + struct zr_toggle toggle; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_style *config; + const struct zr_input *i; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + old = *is_active; + win = ctx->current; + layout = win->layout; + state = zr_toggle_base(&toggle, &bounds, ctx); + if (!state) return 0; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + toggle.touch_pad = zr_get_property(ctx, ZR_PROPERTY_TOUCH_PADDING); + toggle.rounding = config->rounding[ZR_ROUNDING_CHECK]; + toggle.cursor = config->colors[ZR_COLOR_TOGGLE_CURSOR]; + toggle.normal = config->colors[ZR_COLOR_TOGGLE]; + toggle.hover = config->colors[ZR_COLOR_TOGGLE_HOVER]; + zr_do_toggle(&ws, &win->buffer, bounds, is_active, text, ZR_TOGGLE_CHECK, + &toggle, i, &config->font); + return old != *is_active; +} + +void +zr_radio(struct zr_context *ctx, const char *text, int *active) +{ + *active = zr_option(ctx, text, *active); +} + +int +zr_option(struct zr_context *ctx, const char *text, int is_active) +{ + struct zr_rect bounds; + struct zr_toggle toggle; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return is_active; + + win = ctx->current; + layout = win->layout; + state = zr_toggle_base(&toggle, &bounds, ctx); + if (!state) return is_active; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + toggle.touch_pad = zr_get_property(ctx, ZR_PROPERTY_TOUCH_PADDING); + toggle.cursor = config->colors[ZR_COLOR_TOGGLE_CURSOR]; + toggle.normal = config->colors[ZR_COLOR_TOGGLE]; + toggle.hover = config->colors[ZR_COLOR_TOGGLE_HOVER]; + zr_do_toggle(&ws, &win->buffer, bounds, &is_active, text, ZR_TOGGLE_OPTION, + &toggle, i, &config->font); + return is_active; +} + +float +zr_slide_float(struct zr_context *ctx, float min, float val, float max, float step) +{zr_slider_float(ctx, min, &val, max, step); return val;} + +int +zr_slide_int(struct zr_context *ctx, int min, int val, int max, int step) +{zr_slider_int(ctx, min, &val, max, step); return val;} + +void +zr_slider_float(struct zr_context *ctx, float min_value, float *value, + float max_value, float value_step) +{ + struct zr_rect bounds; + struct zr_slider slider; + struct zr_vec2 item_padding; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + state = zr_widget(&bounds, ctx); + if (!state) return; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + slider.padding.x = item_padding.x; + slider.padding.y = item_padding.y; + slider.bg = config->colors[ZR_COLOR_SLIDER]; + slider.normal = config->colors[ZR_COLOR_SLIDER_CURSOR]; + slider.hover = config->colors[ZR_COLOR_SLIDER_CURSOR_HOVER]; + slider.active = config->colors[ZR_COLOR_SLIDER_CURSOR_ACTIVE]; + slider.border = config->colors[ZR_COLOR_BORDER]; + slider.rounding = config->rounding[ZR_ROUNDING_SLIDER]; + *value = zr_do_slider(&ws, &win->buffer, bounds, min_value, *value, max_value, + value_step, &slider, i); +} + +void +zr_slider_int(struct zr_context *ctx, int min_value, int *value, + int max_value, int value_step) +{ + float val = (float)*value; + zr_slider_float(ctx, (float)min_value, &val, (float)max_value, (float)value_step); + *value = (int)val; +} + +void +zr_progress(struct zr_context *ctx, zr_size *cur_value, zr_size max_value, + int is_modifiable) +{ + struct zr_rect bounds; + struct zr_progress prog; + struct zr_vec2 item_padding; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_style *config; + const struct zr_input *i; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + state = zr_widget(&bounds, ctx); + if (!state) return; + i = (state == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + prog.padding.x = item_padding.x; + prog.padding.y = item_padding.y; + prog.border = config->colors[ZR_COLOR_BORDER]; + prog.background = config->colors[ZR_COLOR_PROGRESS]; + prog.normal = config->colors[ZR_COLOR_PROGRESS_CURSOR]; + prog.hover = config->colors[ZR_COLOR_PROGRESS_CURSOR_HOVER]; + prog.active = config->colors[ZR_COLOR_PROGRESS_CURSOR_ACTIVE]; + *cur_value = zr_do_progress(&ws, &win->buffer, bounds, *cur_value, max_value, + is_modifiable, &prog, i); +} + +static enum zr_widget_state +zr_edit_base(struct zr_rect *bounds, struct zr_edit *field, + struct zr_context *ctx) +{ + const struct zr_style *config; + struct zr_vec2 item_padding; + enum zr_widget_state state = zr_widget(bounds, ctx); + if (!state) return state; + + config = &ctx->style; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + field->border_size = 1; + field->scrollbar_width = config->properties[ZR_PROPERTY_SCROLLBAR_SIZE].x; + field->rounding = config->rounding[ZR_ROUNDING_INPUT]; + field->padding.x = item_padding.x; + field->padding.y = item_padding.y; + field->show_cursor = zr_true; + field->background = config->colors[ZR_COLOR_INPUT]; + field->border = config->colors[ZR_COLOR_BORDER]; + field->cursor = config->colors[ZR_COLOR_INPUT_CURSOR]; + field->text = config->colors[ZR_COLOR_INPUT_TEXT]; + field->scroll.rounding = config->rounding[ZR_ROUNDING_SCROLLBAR]; + field->scroll.background = config->colors[ZR_COLOR_SCROLLBAR]; + field->scroll.normal = config->colors[ZR_COLOR_SCROLLBAR_CURSOR]; + field->scroll.hover = config->colors[ZR_COLOR_SCROLLBAR_CURSOR_HOVER]; + field->scroll.active = config->colors[ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE]; + field->scroll.border = config->colors[ZR_COLOR_BORDER]; + return state; +} + +zr_flags +zr_edit_string(struct zr_context *ctx, zr_flags flags, + char *memory, zr_size *len, zr_size max, zr_filter filter) +{ + zr_flags active; + struct zr_buffer buffer; + max = MAX(1, max); + *len = MIN(*len, max-1); + zr_buffer_init_fixed(&buffer, memory, max); + buffer.allocated = *len; + active = zr_edit_buffer(ctx, flags, &buffer, filter); + *len = buffer.allocated; + return active; +} + +zr_flags +zr_edit_buffer(struct zr_context *ctx, zr_flags flags, + struct zr_buffer *buffer, zr_filter filter) +{ + struct zr_window *win; + struct zr_input *i; + zr_flags old_flags, ret_flags = 0; + + enum zr_widget_state state; + struct zr_rect bounds; + struct zr_edit field; + zr_hash hash; + + int *active = 0; + float *scroll = 0; + zr_size *cursor = 0; + struct zr_text_selection *sel = 0; + + /* dummy state for non active edit */ + int dummy_active = 0; + float dummy_scroll = 0; + zr_size dummy_cursor = 0; + struct zr_text_selection dummy_sel = {0,0,0}; + + /* make sure correct values */ + ZR_ASSERT(ctx); + ZR_ASSERT(buffer); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = zr_edit_base(&bounds, &field, ctx); + if (!state) return 0; + i = (state == ZR_WIDGET_ROM || win->layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + if ((flags & ZR_EDIT_READ_ONLY)) { + field.modifiable = 0; + field.show_cursor = 0; + } else { + field.modifiable = 1; + } + + /* check if edit is currently hot item */ + hash = win->edit.seq++; + if (win->edit.active && hash == win->edit.name) { + active = &win->edit.active; + cursor = &win->edit.cursor; + scroll = &win->edit.scrollbar; + sel = &win->edit.sel; + } else { + active = &dummy_active; + cursor = &dummy_cursor; + scroll = &dummy_scroll; + sel = &dummy_sel; + } + + old_flags = (*active) ? ZR_EDIT_ACTIVE: ZR_EDIT_INACTIVE; + if (!flags || flags == ZR_EDIT_CURSOR) { + int old = *active; + i = (flags & ZR_EDIT_READ_ONLY) ? 0: i; + if (!flags) { + /* simple edit field with only appending and removing at the end of the buffer */ + buffer->allocated = zr_widget_edit(&win->buffer, bounds, + (char*)buffer->memory.ptr, buffer->allocated, buffer->memory.size, + active, 0, &field, filter, i, &ctx->style.font); + } else { + /* simple edit field cursor based movement, inserting and removing */ + zr_size glyphs = zr_utf_len((const char*)buffer->memory.ptr, buffer->allocated); + *cursor = MIN(*cursor, glyphs); + buffer->allocated = zr_widget_edit(&win->buffer, bounds, + (char*)buffer->memory.ptr, buffer->allocated , buffer->memory.size, + active, cursor, &field, filter, i, &ctx->style.font); + } + + if (dummy_active) { + /* set hot edit widget state */ + win->edit.active = 1; + win->edit.name = hash; + win->edit.scrollbar = 0; + win->edit.sel.begin = 0; + win->edit.sel.end = 0; + win->edit.cursor = 0; + } else if (old && !*active) { + win->edit.active = 0; + } + } else { + /* editbox based editing either in single line (edit field) or multiline (edit box) */ + struct zr_edit_box box; + if (flags & ZR_EDIT_CLIPBOARD) + zr_edit_box_init_buffer(&box, buffer, &ctx->clip, filter); + else zr_edit_box_init_buffer(&box, buffer, 0, filter); + + box.glyphs = zr_utf_len((const char*)buffer->memory.ptr, buffer->allocated); + box.active = *active; + box.filter = filter; + box.scrollbar = *scroll; + *cursor = MIN(box.glyphs, *cursor); + box.cursor = *cursor; + + if (!(flags & ZR_EDIT_CURSOR)) { + box.sel.begin = box.cursor; + box.sel.end = box.cursor; + } else { + if (!(flags & ZR_EDIT_SELECTABLE)) { + box.sel.active = 0; + box.sel.begin = box.cursor; + box.sel.end = box.cursor; + } else box.sel = *sel; + } + + if (flags & ZR_EDIT_MULTILINE) + zr_widget_edit_box(&win->buffer, bounds, &box, &field, i, &ctx->style.font); + else zr_widget_edit_field(&win->buffer, bounds, &box, &field, i, &ctx->style.font); + + if (box.active) { + /* update hot edit widget state */ + *active = 1; + win->edit.active = 1; + win->edit.name = hash; + win->edit.scrollbar = box.scrollbar; + win->edit.sel = box.sel; + win->edit.cursor = box.cursor; + buffer->allocated = box.buffer.allocated; + } else if (!box.active && *active) { + win->edit.active = 0; + } + } + + if (*active && (flags & ZR_EDIT_SIGCOMIT) && + zr_input_is_key_pressed(i, ZR_KEY_ENTER)) { + ret_flags |= ZR_EDIT_SIGCOMIT; + *active = 0; + } + + /* compress edit widget state and state changes into flags */ + ret_flags |= (*active) ? ZR_EDIT_ACTIVE: ZR_EDIT_INACTIVE; + if (old_flags == ZR_EDIT_INACTIVE && ret_flags & ZR_EDIT_ACTIVE) + ret_flags |= ZR_EDIT_ACTIVATED; + else if (old_flags == ZR_EDIT_ACTIVE && ret_flags & ZR_EDIT_INACTIVE) + ret_flags |= ZR_EDIT_DEACTIVATED; + return ret_flags; +} + +static void +zr_property(struct zr_context *ctx, const char *name, + float min, float *val, float max, float step, + float inc_per_pixel, zr_filter filter) +{ + struct zr_rect bounds; + enum zr_widget_state s; + enum zr_widget_status ws; + struct zr_property prop; + struct zr_vec2 item_padding; + + int *state = 0; + int old_state = 0; + zr_hash hash = 0; + char *buffer = 0; + zr_size *len = 0; + zr_size *cursor = 0; + + char dummy_buffer[ZR_MAX_NUMBER_BUFFER]; + int dummy_state = ZR_PROPERTY_DEFAULT; + zr_size dummy_length = 0; + zr_size dummy_cursor = 0; + + struct zr_window *win; + struct zr_panel *layout; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + win = ctx->current; + layout = win->layout; + config = &ctx->style; + s = zr_widget(&bounds, ctx); + if (!s) return; + i = (s == ZR_WIDGET_ROM || layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + /* calculate hash from name */ + if (name[0] == '#') { + hash = zr_murmur_hash(name, (int)zr_strsiz(name), win->property.seq++); + name++; /* special number hash */ + } else hash = zr_murmur_hash(name, (int)zr_strsiz(name), 42); + + /* check if property is currently hot item */ + if (win->property.active && hash == win->property.name) { + buffer = win->property.buffer; + len = &win->property.length; + cursor = &win->property.cursor; + state = &win->property.state; + } else { + buffer = dummy_buffer; + len = &dummy_length; + cursor = &dummy_cursor; + state = &dummy_state; + } + + /* execute property widget */ + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + prop.border_size = 1; + prop.rounding = config->rounding[ZR_ROUNDING_PROPERTY]; + prop.padding = item_padding; + prop.border = config->colors[ZR_COLOR_BORDER]; + prop.normal = config->colors[ZR_COLOR_PROPERTY]; + prop.hover = config->colors[ZR_COLOR_PROPERTY_HOVER]; + prop.active = config->colors[ZR_COLOR_PROPERTY_ACTIVE]; + prop.text = config->colors[ZR_COLOR_TEXT]; + old_state = *state; + *val = zr_do_property(&ws, &win->buffer, bounds, name, min, *val, max, step, + inc_per_pixel, buffer, len, state, cursor, &prop, filter, i, &config->font); + + if (*state != ZR_PROPERTY_DEFAULT && !win->property.active) { + /* current property is now hot */ + win->property.active = 1; + zr_memcopy(win->property.buffer, buffer, *len); + win->property.length = *len; + win->property.cursor = *cursor; + win->property.state = *state; + win->property.name = hash; + } + /* check if previously active property is now unactive */ + if (*state == ZR_PROPERTY_DEFAULT && old_state != ZR_PROPERTY_DEFAULT) + win->property.active = 0; +} + +void +zr_property_float(struct zr_context *ctx, const char *name, + float min, float *val, float max, float step, float inc_per_pixel) +{ + zr_property(ctx, name, min, val, max, step, inc_per_pixel, zr_filter_float); +} + +void +zr_property_int(struct zr_context *ctx, const char *name, + int min, int *val, int max, int step, int inc_per_pixel) +{ + float value = (float)*val; + zr_property(ctx, name, (float)min, &value, (float)max, (float)step, + (float)inc_per_pixel, zr_filter_decimal); + *val = (int)value; +} + +float +zr_propertyf(struct zr_context *ctx, const char *name, float min, float val, + float max, float step, float inc_per_pixel) +{ + zr_property_float(ctx, name, (float)min, &val, (float)max, + (float)step, (float)inc_per_pixel); + return val; +} + +int +zr_propertyi(struct zr_context *ctx, const char *name, int min, int val, int max, + int step, int inc_per_pixel) +{ + zr_property_int(ctx, name, min, &val, max, step, inc_per_pixel); + return val; +} + +/* ------------------------------------------------------------- + * + * CHART + * + * --------------------------------------------------------------*/ +void +zr_chart_begin(struct zr_context *ctx, enum zr_chart_type type, + zr_size count, float min_value, float max_value) +{ + struct zr_rect bounds = {0, 0, 0, 0}; + struct zr_vec2 item_padding; + struct zr_color color; + + struct zr_command_buffer *out; + const struct zr_style *config; + struct zr_chart *chart; + struct zr_window *win; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) return; + if (!zr_widget(&bounds, ctx)) { + chart = &ctx->current->layout->chart; + zr_zero(chart, sizeof(*chart)); + return; + } + + win = ctx->current; + out = &win->buffer; + config = &ctx->style; + chart = &win->layout->chart; + + /* draw chart background */ + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + color = (type == ZR_CHART_LINES) ? + config->colors[ZR_COLOR_PLOT]: config->colors[ZR_COLOR_HISTO]; + zr_draw_rect(out, bounds, config->rounding[ZR_ROUNDING_CHART], color); + + /* setup basic generic chart */ + zr_zero(chart, sizeof(*chart)); + chart->type = type; + chart->index = 0; + chart->count = count; + chart->min = MIN(min_value, max_value); + chart->max = MAX(min_value, max_value); + chart->range = chart->max - chart->min; + chart->x = bounds.x + item_padding.x; + chart->y = bounds.y + item_padding.y; + chart->w = bounds.w - 2 * item_padding.x; + chart->h = bounds.h - 2 * item_padding.y; + chart->w = MAX(chart->w, 2 * item_padding.x); + chart->h = MAX(chart->h, 2 * item_padding.y); + chart->last.x = 0; chart->last.y = 0; +} + +static zr_flags +zr_chart_push_line(struct zr_context *ctx, struct zr_window *win, + struct zr_chart *g, float value) +{ + zr_flags ret = 0; + struct zr_vec2 cur; + struct zr_rect bounds; + float step, range, ratio; + struct zr_color color; + + struct zr_panel *layout = win->layout; + const struct zr_input *i = &ctx->input; + const struct zr_style *config = &ctx->style; + struct zr_command_buffer *out = &win->buffer; + + step = g->w / (float)g->count; + range = g->max - g->min; + ratio = (value - g->min) / range; + + if (g->index == 0) { + /* special case for the first data point since it does not have a connection */ + g->last.x = g->x; + g->last.y = (g->y + g->h) - ratio * (float)g->h; + + bounds.x = g->last.x - 2; + bounds.y = g->last.y - 2; + bounds.w = 4; + bounds.h = 4; + + color = config->colors[ZR_COLOR_PLOT_LINES]; + if (!(layout->flags & ZR_WINDOW_ROM) && + ZR_INBOX(i->mouse.pos.x,i->mouse.pos.y, g->last.x-3, g->last.y-3, 6, 6)){ + ret = zr_input_is_mouse_hovering_rect(i, bounds) ? ZR_CHART_HOVERING : 0; + ret |= (i->mouse.buttons[ZR_BUTTON_LEFT].down && + i->mouse.buttons[ZR_BUTTON_LEFT].clicked) ? ZR_CHART_CLICKED: 0; + color = config->colors[ZR_COLOR_PLOT_HIGHLIGHT]; + } + zr_draw_rect(out, bounds, 0, color); + g->index++; + return ret; + } + + /* draw a line between the last data point and the new one */ + cur.x = g->x + (float)(step * (float)g->index); + cur.y = (g->y + g->h) - (ratio * (float)g->h); + zr_draw_line(out, g->last.x, g->last.y, cur.x, cur.y, + config->colors[ZR_COLOR_PLOT_LINES]); + + bounds.x = cur.x - 3; + bounds.y = cur.y - 3; + bounds.w = 6; + bounds.h = 6; + + /* user selection of current data point */ + color = config->colors[ZR_COLOR_PLOT_LINES]; + if (!(layout->flags & ZR_WINDOW_ROM)) { + if (zr_input_is_mouse_hovering_rect(i, bounds)) { + ret = ZR_CHART_HOVERING; + ret |= (!i->mouse.buttons[ZR_BUTTON_LEFT].down && + i->mouse.buttons[ZR_BUTTON_LEFT].clicked) ? ZR_CHART_CLICKED: 0; + color = config->colors[ZR_COLOR_PLOT_HIGHLIGHT]; + } + } + zr_draw_rect(out, zr_rect(cur.x - 2, cur.y - 2, 4, 4), 0, color); + + /* save current data point position */ + g->last.x = cur.x; + g->last.y = cur.y; + g->index++; + return ret; +} + +static zr_flags +zr_chart_push_column(const struct zr_context *ctx, struct zr_window *win, + struct zr_chart *chart, float value) +{ + struct zr_command_buffer *out = &win->buffer; + const struct zr_style *config = &ctx->style; + const struct zr_input *in = &ctx->input; + struct zr_panel *layout = win->layout; + + float ratio; + zr_flags ret = 0; + struct zr_color color; + struct zr_rect item = {0,0,0,0}; + + if (chart->index >= chart->count) + return zr_false; + if (chart->count) { + float padding = (float)(chart->count-1); + item.w = (chart->w - padding) / (float)(chart->count); + } + + /* calculate bounds of the current bar chart entry */ + color = config->colors[ZR_COLOR_HISTO_BARS]; + item.h = chart->h * ZR_ABS((value/chart->range)); + if (value >= 0) { + ratio = (value + ZR_ABS(chart->min)) / ZR_ABS(chart->range); + item.y = (chart->y + chart->h) - chart->h * ratio; + } else { + ratio = (value - chart->max) / chart->range; + item.y = chart->y + (chart->h * ZR_ABS(ratio)) - item.h; + } + item.x = chart->x + ((float)chart->index * item.w); + item.x = item.x + ((float)chart->index); + + /* user chart bar selection */ + if (!(layout->flags & ZR_WINDOW_ROM) && + ZR_INBOX(in->mouse.pos.x,in->mouse.pos.y,item.x,item.y,item.w,item.h)) { + ret = ZR_CHART_HOVERING; + ret |= (!in->mouse.buttons[ZR_BUTTON_LEFT].down && + in->mouse.buttons[ZR_BUTTON_LEFT].clicked) ? ZR_CHART_CLICKED: 0; + color = config->colors[ZR_COLOR_HISTO_HIGHLIGHT]; + } + zr_draw_rect(out, item, 0, color); + chart->index++; + return ret; +} + +zr_flags +zr_chart_push(struct zr_context *ctx, float value) +{ + struct zr_window *win; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return zr_false; + + win = ctx->current; + switch (win->layout->chart.type) { + case ZR_CHART_LINES: + return zr_chart_push_line(ctx, win, &win->layout->chart, value); + case ZR_CHART_COLUMN: + return zr_chart_push_column(ctx, win, &win->layout->chart, value); + default: + case ZR_CHART_MAX: + return zr_false; + } +} + +void +zr_chart_end(struct zr_context *ctx) +{ + struct zr_window *win; + struct zr_chart *chart; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return; + + win = ctx->current; + chart = &win->layout->chart; + chart->type = ZR_CHART_MAX; + chart->index = 0; + chart->count = 0; + chart->min = 0; + chart->max = 0; + chart->x = 0; + chart->y = 0; + chart->w = 0; + chart->h = 0; +} + +/* ------------------------------------------------------------- + * + * GROUP + * + * --------------------------------------------------------------*/ +int +zr_group_begin(struct zr_context *ctx, struct zr_panel *layout, + const char *title, zr_flags flags) +{ + union {struct zr_scroll *s; zr_uint *i;} value; + struct zr_rect bounds; + const struct zr_rect *c; + struct zr_window panel; + int title_len; + zr_hash title_hash; + struct zr_window *win; + + ZR_ASSERT(ctx); + ZR_ASSERT(title); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !title) + return 0; + + /* allocate space for the group panel inside the panel */ + win = ctx->current; + c = &win->layout->clip; + zr_panel_alloc_space(&bounds, ctx); + zr_zero(layout, sizeof(*layout)); + + /* find group persistent scrollbar value */ + title_len = (int)zr_strsiz(title); + title_hash = zr_murmur_hash(title, (int)title_len, ZR_WINDOW_SUB); + value.i = zr_find_value(win, title_hash); + if (!value.i) { + value.i = zr_add_value(ctx, win, title_hash, 0); + *value.i = 0; + } + + if (!ZR_INTERSECT(c->x, c->y, c->w, c->h, bounds.x, bounds.y, bounds.w, bounds.h) && + !(flags & ZR_WINDOW_MOVABLE)) { + return 0; + } + + flags |= ZR_WINDOW_SUB; + if (win->flags & ZR_WINDOW_ROM) + flags |= ZR_WINDOW_ROM; + + /* initialize a fake window to create the layout from */ + zr_zero(&panel, sizeof(panel)); + panel.bounds = bounds; + panel.flags = flags; + panel.scrollbar.x = (unsigned short)value.s->x; + panel.scrollbar.y = (unsigned short)value.s->y; + panel.buffer = win->buffer; + panel.layout = layout; + ctx->current = &panel; + zr_panel_begin(ctx, (flags & ZR_WINDOW_TITLE) ? title: 0); + + win->buffer = panel.buffer; + layout->offset = value.s; + layout->parent = win->layout; + win->layout = layout; + ctx->current = win; + return 1; +} + +void +zr_group_end(struct zr_context *ctx) +{ + struct zr_rect clip; + struct zr_window pan; + + struct zr_window *win; + struct zr_panel *parent; + struct zr_panel *g; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return; + + /* make sure zr_group_begin was called correctly */ + ZR_ASSERT(ctx->current); + win = ctx->current; + ZR_ASSERT(win->layout); + g = win->layout; + ZR_ASSERT(g->parent); + parent = g->parent; + + /* dummy window */ + zr_zero(&pan, sizeof(pan)); + pan.bounds = g->bounds; + pan.scrollbar.x = (unsigned short)g->offset->x; + pan.scrollbar.y = (unsigned short)g->offset->y; + pan.flags = g->flags|ZR_WINDOW_SUB; + pan.buffer = win->buffer; + pan.layout = g; + ctx->current = &pan; + + /* make sure group has correct clipping rectangle */ + zr_unify(&clip, &parent->clip, + g->bounds.x, g->clip.y - g->header_h, + g->bounds.x + g->bounds.w+1, + g->bounds.y + g->bounds.h + 1); + zr_draw_scissor(&pan.buffer, clip); + zr_end(ctx); + + win->buffer = pan.buffer; + zr_draw_scissor(&win->buffer, parent->clip); + ctx->current = win; + win->layout = parent; + win->bounds = parent->bounds; +} + +/* -------------------------------------------------------------- + * + * POPUP + * + * --------------------------------------------------------------*/ +int +zr_popup_begin(struct zr_context *ctx, struct zr_panel *layout, + enum zr_popup_type type, const char *title, zr_flags flags, struct zr_rect rect) +{ + zr_hash title_hash; + int title_len; + zr_size allocated; + struct zr_window *popup; + struct zr_window *win; + + ZR_ASSERT(ctx); + ZR_ASSERT(title); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + ZR_ASSERT(!(win->flags & ZR_WINDOW_POPUP)); + title_len = (int)zr_strsiz(title); + title_hash = zr_murmur_hash(title, (int)title_len, ZR_WINDOW_POPUP); + + popup = win->popup.win; + if (!popup) { + popup = (struct zr_window*)zr_create_window(ctx); + win->popup.win = popup; + win->popup.active = 0; + } + + /* make sure we have to correct popup */ + if (win->popup.name != title_hash) { + if (!win->popup.active) { + zr_zero(popup, sizeof(*popup)); + win->popup.name = title_hash; + win->popup.active = 1; + } else return 0; + } + + /* popup position is local to window */ + ctx->current = popup; + rect.x += win->layout->clip.x; + rect.y += win->layout->clip.y; + + /* setup popup data */ + popup->parent = win; + popup->bounds = rect; + popup->seq = ctx->seq; + popup->layout = layout; + popup->flags = flags; + popup->flags |= ZR_WINDOW_BORDER|ZR_WINDOW_SUB|ZR_WINDOW_POPUP; + if (type == ZR_POPUP_DYNAMIC) + popup->flags |= ZR_WINDOW_DYNAMIC; + + popup->buffer = win->buffer; + zr_start_popup(ctx, win); + allocated = ctx->memory.allocated; + zr_draw_scissor(&popup->buffer, zr_null_rect); + + if (zr_panel_begin(ctx, title)) { + /* popup is running therefore invalidate parent window */ + win->layout->flags |= ZR_WINDOW_ROM; + win->layout->flags &= ~(zr_flags)ZR_WINDOW_REMOVE_ROM; + win->popup.active = 1; + layout->offset = &popup->scrollbar; + return 1; + } else { + /* popup was closed/is invalid so cleanup */ + win->layout->flags |= ZR_WINDOW_REMOVE_ROM; + win->layout->popup_buffer.active = 0; + win->popup.active = 0; + ctx->memory.allocated = allocated; + ctx->current = win; + return 0; + } +} + +static int +zr_nonblock_begin(struct zr_panel *layout, struct zr_context *ctx, + zr_flags flags, struct zr_rect body, struct zr_rect header) +{ + int is_active = zr_true; + struct zr_window *popup; + struct zr_window *win; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + /* popups cannot have popups */ + win = ctx->current; + ZR_ASSERT(!(win->flags & ZR_WINDOW_POPUP)); + popup = win->popup.win; + if (!popup) { + /* create window for nonblocking popup */ + popup = (struct zr_window*)zr_create_window(ctx); + win->popup.win = popup; + zr_command_buffer_init(&popup->buffer, &ctx->memory, ZR_CLIPPING_ON); + } else { + /* check if user clicked outside the popup and close if so */ + int in_panel, in_body, in_header; + in_panel = zr_input_is_mouse_click_in_rect(&ctx->input, ZR_BUTTON_LEFT, win->layout->bounds); + in_body = zr_input_is_mouse_click_in_rect(&ctx->input, ZR_BUTTON_LEFT, body); + in_header = zr_input_is_mouse_click_in_rect(&ctx->input, ZR_BUTTON_LEFT, header); + if (!in_body && in_panel && !in_header) + is_active = zr_false; + } + + if (!is_active) { + win->layout->flags |= ZR_WINDOW_REMOVE_ROM; + return is_active; + } + + popup->bounds = body; + popup->parent = win; + popup->layout = layout; + popup->flags = flags; + popup->flags |= ZR_WINDOW_BORDER|ZR_WINDOW_POPUP; + popup->flags |= ZR_WINDOW_DYNAMIC|ZR_WINDOW_SUB; + popup->flags |= ZR_WINDOW_NONBLOCK; + popup->seq = ctx->seq; + win->popup.active = 1; + + zr_start_popup(ctx, win); + popup->buffer = win->buffer; + zr_draw_scissor(&popup->buffer, zr_null_rect); + ctx->current = popup; + + zr_panel_begin(ctx, 0); + win->buffer = popup->buffer; + win->layout->flags |= ZR_WINDOW_ROM; + layout->offset = &popup->scrollbar; + return is_active; +} + +void +zr_popup_close(struct zr_context *ctx) +{ + struct zr_window *popup; + ZR_ASSERT(ctx); + if (!ctx || !ctx->current) return; + popup = ctx->current; + ZR_ASSERT(popup->parent); + ZR_ASSERT(popup->flags & ZR_WINDOW_POPUP); + popup->flags |= ZR_WINDOW_HIDDEN; +} + +void +zr_popup_end(struct zr_context *ctx) +{ + struct zr_window *win; + struct zr_window *popup; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + popup = ctx->current; + ZR_ASSERT(popup->parent); + win = popup->parent; + if (popup->flags & ZR_WINDOW_HIDDEN) { + win->layout->flags |= ZR_WINDOW_REMOVE_ROM; + win->popup.active = 0; + } + zr_draw_scissor(&popup->buffer, zr_null_rect); + zr_end(ctx); + + win->buffer = popup->buffer; + zr_finish_popup(ctx, win); + ctx->current = win; + zr_draw_scissor(&win->buffer, win->layout->clip); +} +/* ------------------------------------------------------------- + * + * TOOLTIP + * + * -------------------------------------------------------------- */ +int +zr_tooltip_begin(struct zr_context *ctx, struct zr_panel *layout, float width) +{ + int ret; + struct zr_rect bounds; + struct zr_window *win; + const struct zr_input *in; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + /* make sure that no nonblocking popup is currently active */ + win = ctx->current; + in = &ctx->input; + if (win->popup.win && (win->popup.win->flags & ZR_WINDOW_NONBLOCK)) + return 0; + + bounds.w = width; + bounds.h = zr_null_rect.h; + bounds.x = (in->mouse.pos.x + 1) - win->layout->clip.x; + bounds.y = (in->mouse.pos.y + 1) - win->layout->clip.y; + + ret = zr_popup_begin(ctx, layout, ZR_POPUP_DYNAMIC, + "__##Tooltip##__", ZR_WINDOW_NO_SCROLLBAR|ZR_WINDOW_TOOLTIP, bounds); + if (ret) win->layout->flags &= ~(zr_flags)ZR_WINDOW_ROM; + return ret; +} + +void +zr_tooltip_end(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + if (!ctx || !ctx->current) + return; + zr_popup_close(ctx); + zr_popup_end(ctx); +} + +void +zr_tooltip(struct zr_context *ctx, const char *text) +{ + zr_size text_len; + zr_size text_width; + zr_size text_height; + + const struct zr_style *config; + struct zr_vec2 item_padding; + struct zr_vec2 padding; + struct zr_panel layout; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + ZR_ASSERT(text); + if (!ctx || !ctx->current || !ctx->current->layout || !text) + return; + + /* fetch configuration data */ + config = &ctx->style; + padding = config->properties[ZR_PROPERTY_PADDING]; + item_padding = config->properties[ZR_PROPERTY_ITEM_PADDING]; + + /* calculate size of the text and tooltip */ + text_len = zr_strsiz(text); + text_width = config->font.width(config->font.userdata, + config->font.height, text, text_len); + text_width += (zr_size)(2 * padding.x + 2 * item_padding.x); + text_height = (zr_size)(config->font.height + 2 * item_padding.y); + + /* execute tooltip and fill with text */ + if (zr_tooltip_begin(ctx, &layout, (float)text_width)) { + zr_layout_row_dynamic(ctx, (float)text_height, 1); + zr_text(ctx, text, text_len, ZR_TEXT_LEFT); + zr_tooltip_end(ctx); + } +} + +/* + * ------------------------------------------------------------- + * + * CONTEXTUAL + * + * -------------------------------------------------------------- + */ +int +zr_contextual_begin(struct zr_context *ctx, struct zr_panel *layout, + zr_flags flags, struct zr_vec2 size, struct zr_rect trigger_bounds) +{ + static const struct zr_rect null_rect = {0,0,0,0}; + int is_clicked = 0; + int is_active = 0; + int is_open = 0; + int ret = 0; + + struct zr_window *win; + struct zr_window *popup; + struct zr_rect body; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + ++win->popup.con_count; + + /* check if currently active contextual is active */ + popup = win->popup.win; + is_open = (popup && (popup->flags & ZR_WINDOW_CONTEXTUAL) && win->popup.type == ZR_WINDOW_CONTEXTUAL); + is_clicked = zr_input_mouse_clicked(&ctx->input, ZR_BUTTON_RIGHT, trigger_bounds); + if (win->popup.active_con && win->popup.con_count != win->popup.active_con) + return 0; + if ((is_clicked && is_open && !is_active) || (!is_open && !is_active && !is_clicked)) + return 0; + + /* calculate contextual position on click */ + win->popup.active_con = win->popup.con_count; + if (is_clicked) { + body.x = ctx->input.mouse.pos.x; + body.y = ctx->input.mouse.pos.y; + } else { + body.x = popup->bounds.x; + body.y = popup->bounds.y; + } + body.w = size.x; + body.h = size.y; + + /* start nonblocking contextual popup */ + ret = zr_nonblock_begin(layout, ctx, + flags|ZR_WINDOW_CONTEXTUAL|ZR_WINDOW_NO_SCROLLBAR, body, null_rect); + if (ret) win->popup.type = ZR_WINDOW_CONTEXTUAL; + else { + win->popup.active_con = 0; + win->popup.win->flags = 0; + } + return ret; +} + +static int +zr_contextual_button(struct zr_context *ctx, const char *text, + zr_flags align, enum zr_button_behavior behavior) +{ + struct zr_button_text button; + struct zr_rect bounds; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + config = &ctx->style; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_FITTING); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || win->layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + button.base.border_width = 0; + button.base.normal = config->colors[ZR_COLOR_WINDOW]; + button.base.border = config->colors[ZR_COLOR_WINDOW]; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + button.alignment = align|ZR_TEXT_MIDDLE; + return zr_do_button_text(&ws, &win->buffer, bounds, text, behavior, + &button, i, &config->font); +} + +static int +zr_contextual_button_symbol(struct zr_context *ctx, enum zr_symbol_type symbol, + const char *text, zr_flags align, enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_text button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + const struct zr_style *config; + const struct zr_input *i; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_FITTING); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || win->layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + button.alignment = ZR_TEXT_CENTERED|ZR_TEXT_MIDDLE; + button.base.border_width = 0; + button.base.normal = config->colors[ZR_COLOR_WINDOW]; + button.base.border = config->colors[ZR_COLOR_WINDOW]; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + return zr_do_button_text_symbol(&ws, &win->buffer, bounds, symbol, text, align, + behavior, &button, &config->font, i); +} + +static int +zr_contextual_button_icon(struct zr_context *ctx, struct zr_image img, + const char *text, zr_flags align, enum zr_button_behavior behavior) +{ + struct zr_rect bounds; + struct zr_button_text button; + enum zr_widget_status ws; + enum zr_widget_state state; + + struct zr_window *win; + const struct zr_input *i; + const struct zr_style *config; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + state = zr_button(&button.base, &bounds, ctx, ZR_BUTTON_FITTING); + if (!state) return zr_false; + i = (state == ZR_WIDGET_ROM || win->layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input; + + config = &ctx->style; + button.alignment = ZR_TEXT_CENTERED|ZR_TEXT_MIDDLE; + button.base.border_width = 0; + button.base.normal = config->colors[ZR_COLOR_WINDOW]; + button.base.border = config->colors[ZR_COLOR_WINDOW]; + button.normal = config->colors[ZR_COLOR_TEXT]; + button.hover = config->colors[ZR_COLOR_TEXT_HOVERING]; + button.active = config->colors[ZR_COLOR_TEXT_ACTIVE]; + return zr_do_button_text_image(&ws, &win->buffer, bounds, img, text, align, + behavior, &button, &config->font, i); +} + +int +zr_contextual_item(struct zr_context *ctx, const char *title, zr_flags align) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + if (zr_contextual_button(ctx, title, align, ZR_BUTTON_DEFAULT)) { + zr_contextual_close(ctx); + return zr_true; + } + return zr_false; +} + +int +zr_contextual_item_icon(struct zr_context *ctx, struct zr_image img, + const char *title, zr_flags align) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + if (zr_contextual_button_icon(ctx, img, title, align, ZR_BUTTON_DEFAULT)){ + zr_contextual_close(ctx); + return zr_true; + } + return zr_false; +} + +int +zr_contextual_item_symbol(struct zr_context *ctx, enum zr_symbol_type symbol, + const char *title, zr_flags align) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + if (zr_contextual_button_symbol(ctx, symbol, title, align, ZR_BUTTON_DEFAULT)){ + zr_contextual_close(ctx); + return zr_true; + } + return zr_false; +} + +void +zr_contextual_close(struct zr_context *ctx) +{ + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return; + + if (!ctx->current) + return; + zr_popup_close(ctx); +} + +void +zr_contextual_end(struct zr_context *ctx) +{ + struct zr_window *popup; + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + popup = ctx->current; + ZR_ASSERT(popup->parent); + if (popup->flags & ZR_WINDOW_HIDDEN) + popup->seq = 0; + zr_popup_end(ctx); + return; +} +/* ------------------------------------------------------------- + * + * COMBO + * + * --------------------------------------------------------------*/ +static int +zr_combo_begin(struct zr_panel *layout, struct zr_context *ctx, struct zr_window *win, + int height, int is_clicked, struct zr_rect header) +{ + int is_open = 0; + int is_active = 0; + struct zr_rect body; + struct zr_window *popup; + zr_hash hash; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + popup = win->popup.win; + body.x = header.x; + body.w = header.w; + body.y = header.y + header.h-1; + body.h = (float)height; + + hash = win->popup.combo_count++; + is_open = (popup && (popup->flags & ZR_WINDOW_COMBO)); + is_active = (popup && (win->popup.name == hash) && win->popup.type == ZR_WINDOW_COMBO); + if ((is_clicked && is_open && !is_active) || (is_open && !is_active) || + (!is_open && !is_active && !is_clicked)) return 0; + if (!zr_nonblock_begin(layout, ctx, ZR_WINDOW_COMBO|ZR_WINDOW_NO_SCROLLBAR, + body, zr_rect(0,0,0,0))) return 0; + + win->popup.type = ZR_WINDOW_COMBO; + win->popup.name = hash; + return 1; +} + +int +zr_combo_begin_text(struct zr_context *ctx, struct zr_panel *layout, + const char *selected, int height) +{ + const struct zr_input *in; + struct zr_window *win; + enum zr_widget_status state; + enum zr_widget_state s; + struct zr_vec2 item_padding; + struct zr_rect header; + int is_active = zr_false; + + ZR_ASSERT(ctx); + ZR_ASSERT(selected); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout || !selected) + return 0; + + win = ctx->current; + s = zr_widget(&header, ctx); + if (s == ZR_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & ZR_WINDOW_ROM || s == ZR_WIDGET_ROM)? 0: &ctx->input; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + if (zr_button_behavior(&state, header, in, ZR_BUTTON_DEFAULT)) + is_active = zr_true; + + /* draw combo box header background and border */ + zr_draw_rect(&win->buffer, header, 0, ctx->style.colors[ZR_COLOR_BORDER]); + zr_draw_rect(&win->buffer, zr_shrink_rect(header, 1), 0, + ctx->style.colors[ZR_COLOR_COMBO]); + + { + /* print currently selected text item */ + struct zr_rect label; + struct zr_text text; + struct zr_symbol sym; + struct zr_rect bounds = {0,0,0,0}; + zr_size text_len = zr_strsiz(selected); + + /* draw selected label */ + text.padding = zr_vec2(0,0); + text.background = ctx->style.colors[ZR_COLOR_COMBO]; + text.text = ctx->style.colors[ZR_COLOR_TEXT]; + + label.x = header.x + item_padding.x; + label.y = header.y + item_padding.y; + label.w = header.w - (header.h + 2 * item_padding.x); + label.h = header.h - 2 * item_padding.y; + zr_widget_text(&win->buffer, label, selected, text_len, &text, + ZR_TEXT_LEFT|ZR_TEXT_MIDDLE, &ctx->style.font); + + /* draw open/close symbol */ + bounds.y = label.y + label.h/2 - ctx->style.font.height/2; + bounds.w = bounds.h = ctx->style.font.height; + bounds.x = (header.x + header.w) - (bounds.w + 2 * item_padding.x); + + sym.type = ZR_SYMBOL_TRIANGLE_DOWN; + sym.background = ctx->style.colors[ZR_COLOR_COMBO]; + sym.foreground = ctx->style.colors[ZR_COLOR_TEXT]; + sym.border_width = 1.0f; + zr_draw_symbol(&win->buffer, &sym, bounds, &ctx->style.font); + } + return zr_combo_begin(layout, ctx, win, height, is_active, header); +} + +int +zr_combo_begin_color(struct zr_context *ctx, struct zr_panel *layout, + struct zr_color color, int height) +{ + enum zr_widget_status state; + enum zr_widget_state s; + struct zr_vec2 item_padding; + struct zr_rect header; + int is_active = zr_false; + struct zr_window *win; + const struct zr_input *in; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + s = zr_widget(&header, ctx); + if (s == ZR_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & ZR_WINDOW_ROM || s == ZR_WIDGET_ROM)? 0: &ctx->input; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + if (zr_button_behavior(&state, header, in, ZR_BUTTON_DEFAULT)) + is_active = !is_active; + + /* draw combo box header background and border */ + zr_draw_rect(&win->buffer, header, 0, ctx->style.colors[ZR_COLOR_BORDER]); + zr_draw_rect(&win->buffer, zr_shrink_rect(header, 1), 0, + ctx->style.colors[ZR_COLOR_COMBO]); + + { + /* print currently selected string */ + struct zr_rect content; + struct zr_symbol sym; + struct zr_rect bounds = {0,0,0,0}; + + /* draw color */ + content.h = header.h - 4 * item_padding.y; + content.y = header.y + 2 * item_padding.y; + content.x = header.x + 2 * item_padding.x; + content.w = header.w - (header.h + 4 * item_padding.x); + zr_draw_rect(&win->buffer, content, 0, color); + + /* draw open/close symbol */ + bounds.y = (header.y + item_padding.y) + (header.h-2.0f*item_padding.y)/2.0f + -ctx->style.font.height/2; + bounds.w = bounds.h = ctx->style.font.height; + bounds.x = (header.x + header.w) - (bounds.w + 2 * item_padding.x); + + sym.type = ZR_SYMBOL_TRIANGLE_DOWN; + sym.background = ctx->style.colors[ZR_COLOR_COMBO]; + sym.foreground = ctx->style.colors[ZR_COLOR_TEXT]; + sym.border_width = 1.0f; + zr_draw_symbol(&win->buffer, &sym, bounds, &ctx->style.font); + } + return zr_combo_begin(layout, ctx, win, height, is_active, header); +} + +int +zr_combo_begin_image(struct zr_context *ctx, struct zr_panel *layout, + struct zr_image img, int height) +{ + struct zr_window *win; + const struct zr_input *in; + enum zr_widget_status state; + struct zr_vec2 item_padding; + struct zr_rect header; + int is_active = zr_false; + enum zr_widget_state s; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + s = zr_widget(&header, ctx); + if (s == ZR_WIDGET_INVALID) + return 0; + + in = (win->layout->flags & ZR_WINDOW_ROM || s == ZR_WIDGET_ROM)? 0: &ctx->input; + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + if (zr_button_behavior(&state, header, in, ZR_BUTTON_DEFAULT)) + is_active = !is_active; + + /* draw combo box header background and border */ + zr_draw_rect(&win->buffer, header, 0, ctx->style.colors[ZR_COLOR_BORDER]); + zr_draw_rect(&win->buffer, zr_shrink_rect(header, 1), 0, + ctx->style.colors[ZR_COLOR_COMBO]); + + { + struct zr_rect bounds = {0,0,0,0}; + struct zr_symbol sym; + struct zr_rect content; + + /* draw image */ + content.h = header.h - 4 * item_padding.y; + content.y = header.y + 2 * item_padding.y; + content.x = header.x + 2 * item_padding.x; + content.w = header.w - (header.h + 4 * item_padding.x); + zr_draw_image(&win->buffer, content, &img); + + /* draw open/close symbol */ + bounds.y = (header.y + item_padding.y) + (header.h-2.0f*item_padding.y)/2.0f + -ctx->style.font.height/2; + bounds.w = bounds.h = ctx->style.font.height; + bounds.x = (header.x + header.w) - (bounds.w + 2 * item_padding.x); + + sym.type = ZR_SYMBOL_TRIANGLE_DOWN; + sym.background = ctx->style.colors[ZR_COLOR_COMBO]; + sym.foreground = ctx->style.colors[ZR_COLOR_TEXT]; + sym.border_width = 1.0f; + zr_draw_symbol(&win->buffer, &sym, bounds, &ctx->style.font); + } + return zr_combo_begin(layout, ctx, win, height, is_active, header); +} + +int +zr_combo_begin_icon(struct zr_context *ctx, struct zr_panel *layout, + const char *selected, struct zr_image img, int height) +{ + struct zr_window *win; + enum zr_widget_status state; + struct zr_vec2 item_padding; + struct zr_rect header; + int is_active = zr_false; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + if (!zr_widget(&header, ctx)) + return 0; + + item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING); + if (zr_button_behavior(&state, header, &ctx->input, ZR_BUTTON_DEFAULT)) + is_active = !is_active; + + /* draw combo box header background and border */ + zr_draw_rect(&win->buffer, header, 0, ctx->style.colors[ZR_COLOR_BORDER]); + zr_draw_rect(&win->buffer, zr_shrink_rect(header, 1), 0, + ctx->style.colors[ZR_COLOR_COMBO]); + + { + zr_size text_len; + struct zr_symbol sym; + struct zr_rect content; + struct zr_rect label, icon; + struct zr_rect bounds = {0,0,0,0}; + + content.h = header.h - 4 * item_padding.y; + content.y = header.y + 2 * item_padding.y; + content.x = header.x + 2 * item_padding.x; + content.w = header.w - (header.h + 4 * item_padding.x); + + /* draw icon */ + icon.x = content.x; + icon.y = content.y; + icon.h = content.h; + icon.w = icon.h; + zr_draw_image(&win->buffer, icon, &img); + + /* draw label */ + label.x = icon.x + icon.w + 2 * item_padding.x; + label.y = content.y; + label.w = (content.x + content.w) - (icon.x + icon.w); + label.h = content.h; + text_len = zr_strsiz(selected); + zr_draw_text(&win->buffer, label, selected, text_len, &ctx->style.font, + ctx->style.colors[ZR_COLOR_WINDOW], ctx->style.colors[ZR_COLOR_TEXT]); + + bounds.y = (header.y + item_padding.y) + (header.h-2.0f*item_padding.y)/2.0f - + ctx->style.font.height/2; + bounds.w = bounds.h = ctx->style.font.height; + bounds.x = (header.x + header.w) - (bounds.w + 2 * item_padding.x); + + /* draw open/close symbol */ + sym.type = ZR_SYMBOL_TRIANGLE_DOWN; + sym.background = ctx->style.colors[ZR_COLOR_COMBO]; + sym.foreground = ctx->style.colors[ZR_COLOR_TEXT]; + sym.border_width = 1.0f; + zr_draw_symbol(&win->buffer, &sym, bounds, &ctx->style.font); + } + return zr_combo_begin(layout, ctx, win, height, is_active, header); +} + +int zr_combo_item(struct zr_context *ctx, const char *title, zr_flags align) +{return zr_contextual_item(ctx, title, align);} + +int zr_combo_item_icon(struct zr_context *ctx, struct zr_image img, + const char *title, zr_flags align) +{return zr_contextual_item_icon(ctx, img, title, align);} + +int zr_combo_item_symbol(struct zr_context *ctx, enum zr_symbol_type symbol, + const char *title, zr_flags align) +{return zr_contextual_item_symbol(ctx, symbol, title, align);} + +void zr_combo_end(struct zr_context *ctx) +{zr_contextual_end(ctx);} + +void zr_combo_close(struct zr_context *ctx) +{zr_contextual_close(ctx);} +/* + * ------------------------------------------------------------- + * + * MENU + * + * -------------------------------------------------------------- + */ +static int +zr_menu_begin(struct zr_panel *layout, struct zr_context *ctx, struct zr_window *win, + const char *id, int is_clicked, struct zr_rect header, float width) +{ + int is_open = 0; + int is_active = 0; + struct zr_rect body; + struct zr_window *popup; + zr_hash hash = zr_murmur_hash(id, (int)zr_strsiz(id), ZR_WINDOW_MENU); + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + body.x = header.x; + body.w = width; + body.y = header.y + header.h; + body.h = (win->layout->bounds.y + win->layout->bounds.h) - body.y; + + popup = win->popup.win; + is_open = (popup && (popup->flags & ZR_WINDOW_MENU)); + is_active = (popup && (win->popup.name == hash) && win->popup.type == ZR_WINDOW_MENU); + if ((is_clicked && is_open && !is_active) || (is_open && !is_active) || + (!is_open && !is_active && !is_clicked)) return 0; + if (!zr_nonblock_begin(layout, ctx, ZR_WINDOW_MENU|ZR_WINDOW_NO_SCROLLBAR, body, header)) + return 0; + win->popup.type = ZR_WINDOW_MENU; + win->popup.name = hash; + return 1; +} + +int +zr_menu_text_begin(struct zr_context *ctx, struct zr_panel *layout, + const char *title, float width) +{ + struct zr_window *win; + const struct zr_input *in; + struct zr_rect header; + int is_clicked = zr_false; + enum zr_widget_status state; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + { + /* execute menu text button for open/closing the popup */ + struct zr_button_text button; + zr_zero(&button, sizeof(button)); + if (!zr_button(&button.base, &header, ctx, ZR_BUTTON_NORMAL)) + return 0; + + win = ctx->current; + button.base.rounding = 0; + button.base.border_width = 0; + button.base.border = ctx->style.colors[ZR_COLOR_WINDOW]; + button.base.normal = ctx->style.colors[ZR_COLOR_WINDOW]; + button.base.active = ctx->style.colors[ZR_COLOR_WINDOW]; + button.alignment = ZR_TEXT_CENTERED|ZR_TEXT_MIDDLE; + button.normal = ctx->style.colors[ZR_COLOR_TEXT]; + button.active = ctx->style.colors[ZR_COLOR_TEXT]; + button.hover = ctx->style.colors[ZR_COLOR_TEXT]; + in = (win->layout->flags & ZR_WINDOW_ROM) ? 0: &ctx->input; + if (zr_do_button_text(&state, &win->buffer, header, + title, ZR_BUTTON_DEFAULT, &button, in, &ctx->style.font)) + is_clicked = zr_true; + } + return zr_menu_begin(layout, ctx, win, title, is_clicked, header, width); +} + +int +zr_menu_icon_begin(struct zr_context *ctx, struct zr_panel *layout, + const char *id, struct zr_image img, float width) +{ + struct zr_window *win; + struct zr_rect header; + int is_clicked = zr_false; + enum zr_widget_status state; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + { + /* execute menu icon button for open/closing the popup */ + struct zr_button_icon button; + zr_zero(&button, sizeof(button)); + if (!zr_button(&button.base, &header, ctx, ZR_BUTTON_NORMAL)) + return 0; + + button.base.rounding = 1; + button.base.border = ctx->style.colors[ZR_COLOR_BORDER]; + button.base.normal = ctx->style.colors[ZR_COLOR_WINDOW]; + button.base.active = ctx->style.colors[ZR_COLOR_WINDOW]; + button.padding = ctx->style.properties[ZR_PROPERTY_ITEM_PADDING]; + if (zr_do_button_image(&state, &win->buffer, header, img, ZR_BUTTON_DEFAULT, + &button, (win->layout->flags & ZR_WINDOW_ROM)?0:&ctx->input)) + is_clicked = zr_true; + } + return zr_menu_begin(layout, ctx, win, id, is_clicked, header, width); +} + +int +zr_menu_symbol_begin(struct zr_context *ctx, struct zr_panel *layout, + const char *id, enum zr_symbol_type sym, float width) +{ + struct zr_window *win; + struct zr_rect header; + int is_clicked = zr_false; + enum zr_widget_status state; + + ZR_ASSERT(ctx); + ZR_ASSERT(ctx->current); + ZR_ASSERT(ctx->current->layout); + if (!ctx || !ctx->current || !ctx->current->layout) + return 0; + + win = ctx->current; + { + /* execute menu symbol button for open/closing the popup */ + struct zr_button_symbol button; + zr_zero(&button, sizeof(button)); + if (!zr_button(&button.base, &header, ctx, ZR_BUTTON_NORMAL)) + return 0; + + button.base.rounding = 1; + button.base.border = ctx->style.colors[ZR_COLOR_BORDER]; + button.base.normal = ctx->style.colors[ZR_COLOR_WINDOW]; + button.base.active = ctx->style.colors[ZR_COLOR_WINDOW]; + button.normal = ctx->style.colors[ZR_COLOR_TEXT]; + button.active = ctx->style.colors[ZR_COLOR_TEXT]; + button.hover = ctx->style.colors[ZR_COLOR_TEXT]; + if (zr_do_button_symbol(&state, &win->buffer, header, sym, ZR_BUTTON_DEFAULT, + &button, (win->layout->flags & ZR_WINDOW_ROM) ? 0 : &ctx->input, + &ctx->style.font)) is_clicked = zr_true; + } + return zr_menu_begin(layout, ctx, win, id, is_clicked, header, width); +} + +int zr_menu_item(struct zr_context *ctx, zr_flags align, const char *title) +{return zr_contextual_item(ctx, title, align);} + +int zr_menu_item_icon(struct zr_context *ctx, struct zr_image img, + const char *title, zr_flags align) +{ return zr_contextual_item_icon(ctx, img, title, align);} + +int zr_menu_item_symbol(struct zr_context *ctx, enum zr_symbol_type symbol, + const char *title, zr_flags align) +{return zr_contextual_item_symbol(ctx, symbol, title, align);} + +void zr_menu_close(struct zr_context *ctx) +{zr_contextual_close(ctx);} + +void +zr_menu_end(struct zr_context *ctx) +{zr_contextual_end(ctx);} + diff --git a/deps/zahnrad/zahnrad.h b/deps/zahnrad/zahnrad.h new file mode 100644 index 0000000000..813f339699 --- /dev/null +++ b/deps/zahnrad/zahnrad.h @@ -0,0 +1,1631 @@ +/* + Copyright (c) 2016 Micha Mettke + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#ifndef ZR_H_ +#define ZR_H_ + +#ifdef __cplusplus +extern "C" { +#endif +/* + * ============================================================== + * + * CONSTANTS + * + * =============================================================== + */ +#define ZR_UTF_INVALID 0xFFFD +#define ZR_UTF_SIZE 4 +/* describes the number of bytes a glyph consists of*/ +#define ZR_INPUT_MAX 16 +/* defines the max number of bytes to be added as text input in one frame */ +#define ZR_MAX_COLOR_STACK 32 +/* Number of temporary configuration color changes that can be stored */ +#define ZR_MAX_ATTRIB_STACK 32 +/* Number of temporary configuration attribute changes that can be stored */ +#define ZR_MAX_FONT_STACK 32 +/* Number of temporary configuration user font changes that can be stored */ +#define ZR_MAX_FONT_HEIGHT_STACK 32 +/* Number of temporary configuration font height changes that can be stored */ +#define ZR_MAX_NUMBER_BUFFER 64 +/* + * ============================================================== + * + * DEFINES + * + * =============================================================== + */ +#define ZR_COMPILE_WITH_FIXED_TYPES 1 +/* setting this define to 1 adds header for fixed sized types + * if 0 each type has to be set to the correct size*/ +#define ZR_COMPILE_WITH_ASSERT 1 +/* setting this define to 1 adds header for the assert macro + IMPORTANT: it also adds the standard library assert so only use it if wanted*/ +#define ZR_COMPILE_WITH_VERTEX_BUFFER 1 +/* setting this to 1 adds a vertex draw command list backend to this + library, which allows you to convert queue commands into vertex draw commands. + If you do not want or need a default backend you can set this flag to zero + and the module of the library will not be compiled */ +#define ZR_COMPILE_WITH_FONT 1 +/* setting this to 1 adds the `stb_truetype` and `stb_rect_pack` header + to this library and provides a default font for font loading and rendering. + If you already have font handling or do not want to use this font handler + you can just set this define to zero and the font module will not be compiled + and the two headers will not be needed. */ +#define ZR_DISABLE_STB_RECT_PACK_IMPLEMENTATION 0 +/* If you already provide the implementation for stb_rect_pack.h in one of your + files you have to define this as 1 to prevent another implementation and the + resulting symbol collision. */ +#define ZR_DISABLE_STB_TRUETYPE_IMPLEMENTATION 0 +/* If you already provide the implementation for stb_truetype.h in one of your + files you have to define this as 1 to prevent another implementation and the + resulting symbol collision. */ +#define ZR_COMPILE_WITH_COMMAND_USERDATA 0 +/* Activating this adds a userdata pointer into each command */ +/* + * =============================================================== + * + * BASIC + * + * =============================================================== + */ +#if ZR_COMPILE_WITH_FIXED_TYPES +#include +typedef uint32_t zr_uint; +typedef uint32_t zr_hash; +typedef uintptr_t zr_size; +typedef uintptr_t zr_ptr; +typedef uint32_t zr_flags; +typedef uint32_t zr_rune; +typedef uint8_t zr_byte; +#else +typedef unsigned int zr_uint; +typedef unsigned int zr_hash; +typedef unsigned long zr_size; +typedef zr_size zr_ptr; +typedef unsigned int zr_flags; +typedef unsigned int zr_rune; +typedef unsigned char zr_byte; +#endif + +#if ZR_COMPILE_WITH_ASSERT +#ifndef ZR_ASSERT +#include +#define ZR_ASSERT(expr) assert(expr) +#endif +#else +#define ZR_ASSERT(expr) +#endif + +struct zr_user_font; +struct zr_edit_box; +struct zr_user_font_glyph; + +/* =============================================================== + * UTILITY + * ===============================================================*/ +#define ZR_UNDEFINED (-1.0f) +#define ZR_FLAG(x) (1 << (x)) + +enum {zr_false, zr_true}; +struct zr_color {zr_byte r,g,b,a;}; +struct zr_vec2 {float x,y;}; +struct zr_vec2i {short x, y;}; +struct zr_rect {float x,y,w,h;}; +struct zr_recti {short x,y,w,h;}; +typedef char zr_glyph[ZR_UTF_SIZE]; +typedef union {void *ptr; int id;} zr_handle; +struct zr_image {zr_handle handle;unsigned short w,h;unsigned short region[4];}; +struct zr_scroll {unsigned short x, y;}; + +/* math */ +struct zr_rect zr_get_null_rect(void); +struct zr_rect zr_rect(float x, float y, float w, float h); +struct zr_vec2 zr_vec2(float x, float y); + +/* UTF-8 */ +zr_size zr_utf_decode(const char*, zr_rune*, zr_size); +zr_size zr_utf_encode(zr_rune, char*, zr_size); +zr_size zr_utf_len(const char*, zr_size byte_len); + +/* color */ +struct zr_color zr_rgba(zr_byte r, zr_byte g, zr_byte b, zr_byte a); +struct zr_color zr_rgb(zr_byte r, zr_byte g, zr_byte b); +struct zr_color zr_rgba_f(float r, float g, float b, float a); +struct zr_color zr_rgb_f(float r, float g, float b); +struct zr_color zr_hsv(zr_byte h, zr_byte s, zr_byte v); +struct zr_color zr_hsv_f(float h, float s, float v); +struct zr_color zr_hsva(zr_byte h, zr_byte s, zr_byte v, zr_byte a); +struct zr_color zr_hsva_f(float h, float s, float v, float a); +struct zr_color zr_rgba32(zr_uint); +zr_uint zr_color32(struct zr_color); +void zr_colorf(float *r, float *g, float *b, float *a, struct zr_color); +void zr_color_hsv(int *out_h, int *out_s, int *out_v, struct zr_color); +void zr_color_hsv_f(float *out_h, float *out_s, float *out_v, struct zr_color); +void zr_color_hsva(int *h, int *s, int *v, int *a, struct zr_color); +void zr_color_hsva_f(float *out_h, float *out_s, float *out_v, + float *out_a, struct zr_color); + +/* image */ +zr_handle zr_handle_ptr(void*); +zr_handle zr_handle_id(int); +struct zr_image zr_image_ptr(void*); +struct zr_image zr_image_id(int); +int zr_image_is_subimage(const struct zr_image* img); +struct zr_image zr_subimage_ptr(void*, unsigned short w, unsigned short h, + struct zr_rect sub_region); +struct zr_image zr_subimage_id(int, unsigned short w, unsigned short h, + struct zr_rect sub_region); + +/* ============================================================== + * + * MEMORY BUFFER + * + * ===============================================================*/ +/* A basic (double)-buffer with linear allocation and resetting as only + freeing policy. The buffers main purpose is to control all memory management + inside the GUI toolkit and still leave memory control as much as possible in + the hand of the user. The memory is provided in three different ways. + The first way is to use a fixed size block of memory to be filled up. + Biggest advantage is a simple memory model. Downside is that if the buffer + is full there is no way to accesses more memory, which fits target + application with a GUI with roughly known memory consumptions. + The second way to manage memory is by extending the fixed size block by + querying information from the buffer about the used size and needed size and + allocate new memory if the buffer is full. While this approach is still + better than just using a fixed size memory block the reallocation still has + one invalid frame as consquence since the used memory information is only + available at the end of the frame which leads to the last way of handling + memory. + The last and most complicated way of handling memory is by allocator + callbacks. The user hereby registers callbacks to be called to allocate and + free memory if needed. While this solves most allocation problems it causes + some loss of flow control on the user side. + + USAGE + ---------------------------- + To instantiate the buffer you either have to call the fixed size or + allocator initialization function and provide a memory block in the first + case and an allocator in the second case. + To allocate memory from the buffer you would call zr_buffer_alloc with a + request memory block size as well as an alignment for the block. + Finally to reset the memory at the end of the frame and when the memory + buffer inside the buffer is no longer needed you would call zr_buffer_reset. + To free all memory that has been allocated by an allocator if the buffer is + no longer being used you have to call zr_buffer_clear. +*/ +struct zr_memory_status { + void *memory; + /* pointer to the currently used memory block inside the referenced buffer*/ + unsigned int type; + /* type of the buffer which is either fixed size or dynamic */ + zr_size size; + /* total size of the memory block */ + zr_size allocated; + /* allocated amount of memory */ + zr_size needed; + /* memory size that would have been allocated if enough memory was present*/ + zr_size calls; + /* number of allocation calls referencing this buffer */ +}; + +struct zr_allocator { + zr_handle userdata; + /* handle to your own allocator */ + void*(*alloc)(zr_handle, zr_size); + /* allocation function pointer */ + /* reallocation pointer of a previously allocated memory block */ + void(*free)(zr_handle, void*); + /* callback function pointer to finally free all allocated memory */ +}; + +enum zr_allocation_type { + ZR_BUFFER_FIXED, + /* fixed size memory buffer */ + ZR_BUFFER_DYNAMIC + /* dynamically growing buffer */ +}; + +enum zr_buffer_allocation_type { + ZR_BUFFER_FRONT, + /* allocate memory from the front of the buffer */ + ZR_BUFFER_BACK, + /* allocate memory from the back of the buffer */ + ZR_BUFFER_MAX +}; + +struct zr_buffer_marker { + int active; + /* flag indiciation if the marker was set */ + zr_size offset; + /* offset of the marker inside the buffer */ +}; + +struct zr_memory {void *ptr;zr_size size;}; +struct zr_buffer { + struct zr_buffer_marker marker[ZR_BUFFER_MAX]; + /* buffer marker to free a buffer to a certain offset */ + struct zr_allocator pool; + /* allocator callback for dynamic buffers */ + enum zr_allocation_type type; + /* memory management type */ + struct zr_memory memory; + /* memory and size of the current memory block */ + float grow_factor; + /* growing factor for dynamic memory management */ + zr_size allocated; + /* total amount of memory allocated */ + zr_size needed; + /* totally consumed memory given that enough memory is present */ + zr_size calls; + /* number of allocation calls */ + zr_size size; + /* current size of the buffer */ +}; + +void zr_buffer_init(struct zr_buffer*, const struct zr_allocator*, zr_size size); +void zr_buffer_init_fixed(struct zr_buffer*, void *memory, zr_size size); +void zr_buffer_info(struct zr_memory_status*, struct zr_buffer*); +void zr_buffer_free(struct zr_buffer*); +void *zr_buffer_memory(struct zr_buffer*); +zr_size zr_buffer_total(struct zr_buffer*); + +/* =============================================================== + * + * FONT + * + * ===============================================================*/ +/* Font handling in this library can be achived in three different ways. + The first and simplest ways is by just using your font handling mechanism + and provide a simple callback for text string width calculation with + `zr_user_font`. This requires the default drawing output + and is not possible for the optional vertex buffer output. + + The second way of font handling is by using the same `zr_user_font` struct + to referencing a font as before but providing a second callback for + `zr_user_font_glyph` querying which is used for text drawing in the optional + vertex buffer output. In addition to the callback it is also required to + provide a texture atlas from the font to draw. + + The final and most complex way is to use the optional font baker + and font handling function, which requires two additional headers for + TTF font baking. While the previous two methods did no need any functions + outside callbacks and are therefore rather simple to handle, the final + font handling method is quite complex and you need to handle the complex + font baking API. The reason why it is complex is because there are multible + ways of using the API. For example it must be possible to use the font + for default command output as well as vertex buffer output. So for example + texture coordinates can either be UV for vertex buffer output or absolute + pixel for drawing function based on pixels. Furthermore it is possible to + incoperate custom user data into the resulting baked image (for example a + white pixel for the vertex buffer output). + In addition and probably the most complex aspect of the baking API was to + incoperate baking of multible fonts into one image. + + In general the font baking API can be understood as having a number of + loaded in memory TTF-fonts, font baking configuration and optional custom + render data as input, while the output is made of font specific data, a big + glyph array of all baked glyphs and the baked image. The API + was designed that way to have a typical file format and not + a perfectly ready in memory library instance of a font. The reason is more + control and seperates the font baking code from the in library used font + format. +*/ +typedef zr_size(*zr_text_width_f)(zr_handle, float h, const char*, zr_size len); +typedef void(*zr_query_font_glyph_f)(zr_handle handle, float font_height, + struct zr_user_font_glyph *glyph, + zr_rune codepoint, zr_rune next_codepoint); + +#if ZR_COMPILE_WITH_VERTEX_BUFFER +struct zr_user_font_glyph { + struct zr_vec2 uv[2]; + /* texture coordinates */ + struct zr_vec2 offset; + /* offset between top left and glyph */ + float width, height; + /* size of the glyph */ + float xadvance; + /* offset to the next glyph */ +}; +#endif + +struct zr_user_font { + zr_handle userdata; + /* user provided font handle */ + float height; + /* max height of the font */ + zr_text_width_f width; + /* font string width in pixel callback */ +#if ZR_COMPILE_WITH_VERTEX_BUFFER + zr_query_font_glyph_f query; + /* font glyph callback to query drawing info */ + zr_handle texture; + /* texture handle to the used font atlas or texture */ +#endif +}; + +#ifdef ZR_COMPILE_WITH_FONT +enum zr_font_coord_type { + ZR_COORD_UV, + /* texture coordinates inside font glyphs are clamped between 0-1 */ + ZR_COORD_PIXEL + /* texture coordinates inside font glyphs are in absolute pixel */ +}; + +struct zr_baked_font { + float height; + /* height of the font */ + float ascent, descent; + /* font glyphs ascent and descent */ + zr_rune glyph_offset; + /* glyph array offset inside the font glyph baking output array */ + zr_rune glyph_count; + /* number of glyphs of this font inside the glyph baking array output */ + const zr_rune *ranges; + /* font codepoint ranges as pairs of (from/to) and 0 as last element */ +}; + +struct zr_font_config { + void *ttf_blob; + /* pointer to loaded TTF file memory block */ + zr_size ttf_size; + /* size of the loaded TTF file memory block */ + float size; + /* bake pixel height of the font */ + zr_rune oversample_h, oversample_v; + /* rasterize at hight quality for sub-pixel position */ + int pixel_snap; + /* align very character to pixel boundry (if true set oversample (1,1)) */ + enum zr_font_coord_type coord_type; + /* texture coordinate format with either pixel or UV coordinates */ + struct zr_vec2 spacing; + /* extra pixel spacing between glyphs */ + const zr_rune *range; + /* list of unicode ranges (2 values per range, zero terminated) */ + struct zr_baked_font *font; + /* font to setup in the baking process */ +}; + +struct zr_font_glyph { + zr_rune codepoint; + /* unicode codepoint */ + float xadvance; + /* xoffset to the next character */ + float x0, y0, x1, y1, w, h; + /* glyph bounding points in pixel inside the glyph image with top + * left and bottom right */ + float u0, v0, u1, v1; + /* texture coordinates either in pixel or clamped (0.0 - 1.0) */ +}; + +struct zr_font { + float size; + /* pixel height of the font */ + float scale; + /* scale factor for different font size */ + float ascent, descent; + /* font ascent and descent */ + struct zr_font_glyph *glyphs; + /* font glyph array */ + const struct zr_font_glyph *fallback; + /* fallback glyph */ + zr_rune fallback_codepoint; + /* fallback glyph codepoint */ + zr_rune glyph_count; + /* font glyph array size */ + const zr_rune *ranges; + /* glyph unicode ranges in the font */ + zr_handle atlas; + /* font image atlas handle */ +}; + +/* some language glyph codepoint ranges */ +const zr_rune *zr_font_default_glyph_ranges(void); +const zr_rune *zr_font_chinese_glyph_ranges(void); +const zr_rune *zr_font_cyrillic_glyph_ranges(void); +const zr_rune *zr_font_korean_glyph_ranges(void); + +/* font baking functions (need to be called sequentially top to bottom) */ +void zr_font_bake_memory(zr_size *temporary_memory, int *glyph_count, + struct zr_font_config*, int count); +/* this function calculates the needed memory for the baking process + Input: + - array of configuration for every font that should be baked into one image + - number of configuration fonts in the array + Output: + - amount of memory needed in the baking process + - total number of glyphs that need to be allocated +*/ +int zr_font_bake_pack(zr_size *img_memory, int *img_width, int *img_height, + struct zr_recti *custom_space, + void *temporary_memory, zr_size temporary_size, + const struct zr_font_config*, int font_count); +/* this function packs together all glyphs and optional space into one + total image space and returns the needed image width and height. + Input: + - NULL or custom space inside the image (will be modifed to fit!) + - temporary memory block that will be used in the baking process + - size of the temporary memory block + - array of configuration for every font that should be baked into one image + - number of configuration fonts in the array + Output: + - calculated resulting size of the image in bytes + - pixel width of the resulting image + - pixel height of the resulting image + - custom space bounds with position and size inside image which can be + filled by the user +*/ +void zr_font_bake(void *image_memory, int image_width, int image_height, + void *temporary_memory, zr_size temporary_memory_size, + struct zr_font_glyph*, int glyphs_count, + const struct zr_font_config*, int font_count); +/* this function bakes all glyphs into the pre-allocated image and + fills a glyph array with information. + Input: + - image memory buffer to bake the glyph into + - pixel width/height of the image + - temporary memory block that will be used in the baking process + - size of the temporary memory block + Output: + - image filled with glyphs + - filled glyph array +*/ +void zr_font_bake_custom_data(void *img_memory, int img_width, int img_height, + struct zr_recti img_dst, const char *image_data_mask, + int tex_width, int tex_height,char white,char black); +/* this function bakes custom data in string format with white, black and zero + alpha pixels into the font image. The zero alpha pixel is represented as + any character beside the black and zero pixel character. + Input: + - image memory buffer to bake the custom data into + - image size (width/height) of the image in pixels + - custom texture data in string format + - texture size (width/height) of the custom image content + - character representing a white pixel in the texture data format + - character representing a black pixel in the texture data format + Output: + - image filled with custom texture data +*/ +void zr_font_bake_convert(void *out_memory, int image_width, int image_height, + const void *in_memory); +/* this function converts alpha8 baking input image into a rgba8 output image.*/ +void zr_font_init(struct zr_font*, float pixel_height, zr_rune fallback_codepoint, + struct zr_font_glyph*, const struct zr_baked_font*, + zr_handle atlas); +struct zr_user_font zr_font_ref(struct zr_font*); +const struct zr_font_glyph* zr_font_find_glyph(struct zr_font*, zr_rune unicode); + +#endif +/* =============================================================== + * + * RENDERING + * + * ===============================================================*/ +/* This library was designed to be render backend agnostic so it does + not draw anything to the screen. Instead all drawn primitives, widgets + are made of, are buffered into memory and make up a command queue. + Each frame therefore fills the command buffer with draw commands + that then need to be executed by the user and his own render backend. + After that the command buffer needs to be cleared and a new frame can be + started. + + The reason for buffering simple primitives as draw commands instead of + directly buffering a hardware accessible format with vertex and element + buffer was to support native render backends like X11 and Win32. + That being said it is possible to convert the command buffer into a + hardware accessible format to support hardware based rendering as well. +*/ +enum zr_command_type { + ZR_COMMAND_NOP, + ZR_COMMAND_SCISSOR, + ZR_COMMAND_LINE, + ZR_COMMAND_CURVE, + ZR_COMMAND_RECT, + ZR_COMMAND_CIRCLE, + ZR_COMMAND_ARC, + ZR_COMMAND_TRIANGLE, + ZR_COMMAND_TEXT, + ZR_COMMAND_IMAGE +}; + +/* command base and header of every comand inside the buffer */ +struct zr_command { + enum zr_command_type type; + /* the type of the current command */ + zr_size next; + /* absolute base pointer offset to the next command */ +#if ZR_COMPILE_WITH_COMMAND_USERDATA + zr_handle userdata; +#endif +}; + +struct zr_command_scissor { + struct zr_command header; + short x, y; + unsigned short w, h; +}; + +struct zr_command_line { + struct zr_command header; + struct zr_vec2i begin; + struct zr_vec2i end; + struct zr_color color; +}; + +struct zr_command_curve { + struct zr_command header; + struct zr_vec2i begin; + struct zr_vec2i end; + struct zr_vec2i ctrl[2]; + struct zr_color color; +}; + +struct zr_command_rect { + struct zr_command header; + unsigned int rounding; + short x, y; + unsigned short w, h; + struct zr_color color; +}; + +struct zr_command_circle { + struct zr_command header; + short x, y; + unsigned short w, h; + struct zr_color color; +}; + +struct zr_command_arc { + struct zr_command header; + short cx, cy; + unsigned short r; + float a[2]; + struct zr_color color; +}; + +struct zr_command_triangle { + struct zr_command header; + struct zr_vec2i a; + struct zr_vec2i b; + struct zr_vec2i c; + struct zr_color color; +}; + +struct zr_command_image { + struct zr_command header; + short x, y; + unsigned short w, h; + struct zr_image img; +}; + +struct zr_command_text { + struct zr_command header; + const struct zr_user_font *font; + struct zr_color background; + struct zr_color foreground; + short x, y; + unsigned short w, h; + float height; + zr_size length; + char string[1]; +}; + +enum zr_command_clipping { + ZR_CLIPPING_OFF = zr_false, + ZR_CLIPPING_ON = zr_true +}; + +struct zr_command_buffer { + struct zr_buffer *base; + /* memory buffer to store the command */ + struct zr_rect clip; + /* current clipping rectangle */ + int use_clipping; + /* flag if the command buffer should clip commands */ + zr_handle userdata; + /* userdata provided in each command */ + zr_size begin, end, last; +}; + +#if ZR_COMPILE_WITH_VERTEX_BUFFER +typedef unsigned short zr_draw_index; +typedef zr_uint zr_draw_vertex_color; + +enum zr_anti_aliasing { + ZR_ANTI_ALIASING_OFF = zr_false, + /* renderes all primitives without anti-aliasing */ + ZR_ANTI_ALIASING_ON + /* renderes all primitives with anti-aliasing */ +}; + +struct zr_draw_vertex { + struct zr_vec2 position; + struct zr_vec2 uv; + zr_draw_vertex_color col; +}; + +struct zr_draw_command { + unsigned int elem_count; + /* number of elements in the current draw batch */ + struct zr_rect clip_rect; + /* current screen clipping rectangle */ + zr_handle texture; + /* current texture to set */ +#if ZR_COMPILE_WITH_COMMAND_USERDATA + zr_handle userdata; +#endif +}; + +struct zr_draw_null_texture { + zr_handle texture; + /* texture handle to a texture with a white pixel */ + struct zr_vec2 uv; + /* coordinates to the white pixel in the texture */ +}; + +/* drawing routines for custom widgets */ +void zr_draw_scissor(struct zr_command_buffer*, struct zr_rect); +void zr_draw_line(struct zr_command_buffer*, float, float, float, + float, struct zr_color); +void zr_draw_curve(struct zr_command_buffer*, float, float, float, float, + float, float, float, float, struct zr_color); +void zr_draw_rect(struct zr_command_buffer*, struct zr_rect, + float rounding, struct zr_color); +void zr_draw_circle(struct zr_command_buffer*, struct zr_rect, struct zr_color); +void zr_draw_arc(struct zr_command_buffer*, float cx, float cy, float radius, + float a_min, float a_max, struct zr_color); +void zr_draw_triangle(struct zr_command_buffer*, float, float, float, float, + float, float, struct zr_color); +void zr_draw_image(struct zr_command_buffer*, struct zr_rect, struct zr_image*); +void zr_draw_text(struct zr_command_buffer*, struct zr_rect, + const char *text, zr_size len, const struct zr_user_font*, + struct zr_color, struct zr_color); + +#endif +/* =============================================================== + * + * GUI + * + * ===============================================================*/ +enum zr_keys { + ZR_KEY_SHIFT, + ZR_KEY_DEL, + ZR_KEY_ENTER, + ZR_KEY_TAB, + ZR_KEY_BACKSPACE, + ZR_KEY_COPY, + ZR_KEY_CUT, + ZR_KEY_PASTE, + ZR_KEY_LEFT, + ZR_KEY_RIGHT, + ZR_KEY_MAX +}; + +/* every used mouse button */ +enum zr_buttons { + ZR_BUTTON_LEFT, + ZR_BUTTON_MIDDLE, + ZR_BUTTON_RIGHT, + ZR_BUTTON_MAX +}; + +struct zr_mouse_button { + int down; + /* current button state */ + unsigned int clicked; + /* button state change */ + struct zr_vec2 clicked_pos; + /* mouse position of last state change */ +}; + +struct zr_mouse { + struct zr_mouse_button buttons[ZR_BUTTON_MAX]; + /* mouse button states */ + struct zr_vec2 pos; + /* current mouse position */ + struct zr_vec2 prev; + /* mouse position in the last frame */ + struct zr_vec2 delta; + /* mouse travelling distance from last to current frame */ + float scroll_delta; + /* number of steps in the up or down scroll direction */ +}; + +struct zr_key { + int down; + unsigned int clicked; +}; + +struct zr_keyboard { + struct zr_key keys[ZR_KEY_MAX]; + /* state of every used key */ + char text[ZR_INPUT_MAX]; + /* utf8 text input frame buffer */ + zr_size text_len; + /* text input frame buffer length in bytes */ +}; + +struct zr_input { + struct zr_keyboard keyboard; + /* current keyboard key + text input state */ + struct zr_mouse mouse; + /* current mouse button and position state */ +}; + +/* query input state */ +int zr_input_has_mouse_click_in_rect(const struct zr_input*, + enum zr_buttons, struct zr_rect); +int zr_input_has_mouse_click_down_in_rect(const struct zr_input*, enum zr_buttons, + struct zr_rect, int down); +int zr_input_is_mouse_click_in_rect(const struct zr_input*, + enum zr_buttons, struct zr_rect); +int zr_input_any_mouse_click_in_rect(const struct zr_input*, struct zr_rect); +int zr_input_is_mouse_prev_hovering_rect(const struct zr_input*, struct zr_rect); +int zr_input_is_mouse_hovering_rect(const struct zr_input*, struct zr_rect); +int zr_input_mouse_clicked(const struct zr_input*, enum zr_buttons, struct zr_rect); +int zr_input_is_mouse_down(const struct zr_input*, enum zr_buttons); +int zr_input_is_mouse_pressed(const struct zr_input*, enum zr_buttons); +int zr_input_is_mouse_released(const struct zr_input*, enum zr_buttons); +int zr_input_is_key_pressed(const struct zr_input*, enum zr_keys); +int zr_input_is_key_released(const struct zr_input*, enum zr_keys); +int zr_input_is_key_down(const struct zr_input*, enum zr_keys); + +/* ============================================================== + * STYLE + * ===============================================================*/ +enum zr_style_colors { + ZR_COLOR_TEXT, + ZR_COLOR_TEXT_HOVERING, + ZR_COLOR_TEXT_ACTIVE, + ZR_COLOR_WINDOW, + ZR_COLOR_HEADER, + ZR_COLOR_BORDER, + ZR_COLOR_BUTTON, + ZR_COLOR_BUTTON_HOVER, + ZR_COLOR_BUTTON_ACTIVE, + ZR_COLOR_TOGGLE, + ZR_COLOR_TOGGLE_HOVER, + ZR_COLOR_TOGGLE_CURSOR, + ZR_COLOR_SELECTABLE, + ZR_COLOR_SELECTABLE_HOVER, + ZR_COLOR_SELECTABLE_TEXT, + ZR_COLOR_SLIDER, + ZR_COLOR_SLIDER_CURSOR, + ZR_COLOR_SLIDER_CURSOR_HOVER, + ZR_COLOR_SLIDER_CURSOR_ACTIVE, + ZR_COLOR_PROGRESS, + ZR_COLOR_PROGRESS_CURSOR, + ZR_COLOR_PROGRESS_CURSOR_HOVER, + ZR_COLOR_PROGRESS_CURSOR_ACTIVE, + ZR_COLOR_PROPERTY, + ZR_COLOR_PROPERTY_HOVER, + ZR_COLOR_PROPERTY_ACTIVE, + ZR_COLOR_INPUT, + ZR_COLOR_INPUT_CURSOR, + ZR_COLOR_INPUT_TEXT, + ZR_COLOR_COMBO, + ZR_COLOR_HISTO, + ZR_COLOR_HISTO_BARS, + ZR_COLOR_HISTO_HIGHLIGHT, + ZR_COLOR_PLOT, + ZR_COLOR_PLOT_LINES, + ZR_COLOR_PLOT_HIGHLIGHT, + ZR_COLOR_SCROLLBAR, + ZR_COLOR_SCROLLBAR_CURSOR, + ZR_COLOR_SCROLLBAR_CURSOR_HOVER, + ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE, + ZR_COLOR_TABLE_LINES, + ZR_COLOR_TAB_HEADER, + ZR_COLOR_SCALER, + ZR_COLOR_COUNT +}; + +enum zr_style_rounding { + ZR_ROUNDING_BUTTON, + ZR_ROUNDING_SLIDER, + ZR_ROUNDING_CHECK, + ZR_ROUNDING_INPUT, + ZR_ROUNDING_PROPERTY, + ZR_ROUNDING_CHART, + ZR_ROUNDING_SCROLLBAR, + ZR_ROUNDING_MAX +}; + +enum zr_style_properties { + ZR_PROPERTY_ITEM_SPACING, + /* space between widgets */ + ZR_PROPERTY_ITEM_PADDING, + /* padding inside widet between content */ + ZR_PROPERTY_TOUCH_PADDING, + /* extra padding for touch devices */ + ZR_PROPERTY_PADDING, + /* padding between window and widgets */ + ZR_PROPERTY_SCALER_SIZE, + /* width and height of the window scaler */ + ZR_PROPERTY_SCROLLBAR_SIZE, + /* width for vertical scrollbar and height for horizontal scrollbar*/ + ZR_PROPERTY_SIZE, + /* min size of a window that cannot be undercut */ + ZR_PROPERTY_MAX +}; + +enum zr_style_header_align { + ZR_HEADER_LEFT, + ZR_HEADER_RIGHT +}; + +enum zr_style_components { + ZR_DEFAULT_COLOR = 0x01, + /* default all colors inside the configuration struct */ + ZR_DEFAULT_PROPERTIES = 0x02, + /* default all properites inside the configuration struct */ + ZR_DEFAULT_ROUNDING = 0x04, + /* default all rounding values inside the configuration struct */ + ZR_DEFAULT_ALL = 0xFFFF + /* default the complete configuration struct */ +}; + +struct zr_saved_property { + enum zr_style_properties type; + /* identifier of the current modified property */ + struct zr_vec2 value; + /* property value that has been saveed */ +}; + +struct zr_saved_color { + enum zr_style_colors type; + /* identifier of the current modified color */ + struct zr_color value; + /* color value that has been saveed */ +}; + +struct zr_saved_font { + struct zr_user_font value; + /* user font reference */ + int font_height_begin; + /* style font height stack begin */ + int font_height_end; + /* user font height stack end */ +}; + +struct zr_style_mod_stack { + zr_size property; + /* current property stack pushing index */ + struct zr_saved_property properties[ZR_MAX_ATTRIB_STACK]; + /* saved property stack */ + int color; + /* current color stack pushing index */ + struct zr_saved_color colors[ZR_MAX_COLOR_STACK]; + /* saved color stack */ + int font; + /* current font stack pushing index */ + struct zr_saved_font fonts[ZR_MAX_FONT_STACK]; + /* saved user font stack */ + int font_height; + /* current font stack pushing index */ + float font_heights[ZR_MAX_FONT_HEIGHT_STACK]; + /* saved user font stack */ +}; + +struct zr_style_header { + enum zr_style_header_align align; + /* header content alignment */ + zr_rune close_symbol; + /* header close icon unicode rune */ + zr_rune minimize_symbol; + /* header minimize icon unicode rune */ + zr_rune maximize_symbol; + /* header maximize icon unicode rune */ +}; + +struct zr_style { + struct zr_user_font font; + /* the from the user provided font */ + float rounding[ZR_ROUNDING_MAX]; + /* rectangle widget rounding */ + struct zr_style_header header; + /* window header style */ + struct zr_vec2 properties[ZR_PROPERTY_MAX]; + /* configuration properties to modify the style */ + struct zr_color colors[ZR_COLOR_COUNT]; + /* configuration color to modify color */ + struct zr_style_mod_stack stack; + /* modification stack */ +}; + +/*=============================================================== + * EDIT BOX + * ===============================================================*/ +typedef int(*zr_filter)(const struct zr_edit_box*, zr_rune unicode); +typedef void(*zr_paste_f)(zr_handle, struct zr_edit_box*); +typedef void(*zr_copy_f)(zr_handle, const char*, zr_size size); + +/* filter function */ +struct zr_edit_box; +int zr_filter_default(const struct zr_edit_box*, zr_rune unicode); +int zr_filter_ascii(const struct zr_edit_box*, zr_rune unicode); +int zr_filter_float(const struct zr_edit_box*, zr_rune unicode); +int zr_filter_decimal(const struct zr_edit_box*, zr_rune unicode); +int zr_filter_hex(const struct zr_edit_box*, zr_rune unicode); +int zr_filter_oct(const struct zr_edit_box*, zr_rune unicode); +int zr_filter_binary(const struct zr_edit_box*, zr_rune unicode); + +enum zr_edit_remove_operation { + ZR_DELETE = 0, + ZR_REMOVE +}; + +enum zr_edit_flags { + ZR_EDIT_READ_ONLY = ZR_FLAG(0), + /* text inside the edit widget cannot be modified */ + ZR_EDIT_CURSOR = ZR_FLAG(1), + /* edit widget will have a movable cursor */ + ZR_EDIT_SELECTABLE = ZR_FLAG(2), + /* edit widget allows text selection */ + ZR_EDIT_CLIPBOARD = ZR_FLAG(3), + /* edit widget tries to use the clipbard callback for copy & paste */ + ZR_EDIT_SIGCOMIT = ZR_FLAG(4), + /* edit widget generateds ZR_EDIT_COMMITED event on enter */ + ZR_EDIT_MULTILINE = ZR_FLAG(5) + /* edit widget with text wrapping text editing */ +}; + +enum zr_edit_types { + ZR_EDIT_SIMPLE = 0, + ZR_EDIT_FIELD = (ZR_EDIT_CURSOR|ZR_EDIT_SELECTABLE|ZR_EDIT_CLIPBOARD), + ZR_EDIT_BOX = (ZR_EDIT_CURSOR|ZR_EDIT_SELECTABLE| + ZR_EDIT_CLIPBOARD|ZR_EDIT_MULTILINE) +}; + +enum zr_edit_events { + ZR_EDIT_ACTIVE = ZR_FLAG(0), + /* edit widget is currently being modified */ + ZR_EDIT_INACTIVE = ZR_FLAG(1), + /* edit widget is not active and is not being modified */ + ZR_EDIT_ACTIVATED = ZR_FLAG(2), + /* edit widget went from state inactive to state active */ + ZR_EDIT_DEACTIVATED = ZR_FLAG(3), + /* edit widget went from state active to state inactive */ + ZR_EDIT_COMMITED = ZR_FLAG(4) + /* edit widget has received an enter and lost focus */ +}; + +/* editbox */ +void zr_edit_box_clear(struct zr_edit_box*); +void zr_edit_box_add(struct zr_edit_box*, const char*, zr_size); +void zr_edit_box_remove(struct zr_edit_box*, enum zr_edit_remove_operation); +char *zr_edit_box_get(struct zr_edit_box*); +const char *zr_edit_box_get_const(const struct zr_edit_box*); +void zr_edit_box_at(struct zr_edit_box*, zr_size pos, zr_glyph, zr_size*); +void zr_edit_box_at_cursor(struct zr_edit_box*, zr_glyph, zr_size*); +char zr_edit_box_at_char(struct zr_edit_box*, zr_size pos); +void zr_edit_box_set_cursor(struct zr_edit_box*, zr_size pos); +zr_size zr_edit_box_get_cursor(struct zr_edit_box *eb); +zr_size zr_edit_box_len_char(struct zr_edit_box*); +zr_size zr_edit_box_len(struct zr_edit_box*); +int zr_edit_box_has_selection(const struct zr_edit_box*); +const char *zr_edit_box_get_selection(zr_size *len, struct zr_edit_box*); + +/*============================================================== + * WINDOW + * =============================================================*/ + +enum zr_modify { + ZR_FIXED = zr_false, + ZR_MODIFIABLE = zr_true +}; + +enum zr_symbol_type { + ZR_SYMBOL_X, + ZR_SYMBOL_UNDERSCORE, + ZR_SYMBOL_CIRCLE, + ZR_SYMBOL_CIRCLE_FILLED, + ZR_SYMBOL_RECT, + ZR_SYMBOL_RECT_FILLED, + ZR_SYMBOL_TRIANGLE_UP, + ZR_SYMBOL_TRIANGLE_DOWN, + ZR_SYMBOL_TRIANGLE_LEFT, + ZR_SYMBOL_TRIANGLE_RIGHT, + ZR_SYMBOL_PLUS, + ZR_SYMBOL_MINUS, + ZR_SYMBOL_MAX +}; + +enum zr_orientation { + ZR_VERTICAL, + ZR_HORIZONTAL +}; + +enum zr_widget_status { + ZR_INACTIVE, + ZR_HOVERED, + ZR_ACTIVE +}; + +enum zr_collapse_states { + ZR_MINIMIZED = zr_false, + ZR_MAXIMIZED = zr_true +}; + +enum zr_widget_state { + ZR_WIDGET_INVALID, + /* The widget cannot be seen and is completly out of view */ + ZR_WIDGET_VALID, + /* The widget is completly inside the window can be updated */ + ZR_WIDGET_ROM + /* The widget is partially visible and cannot be updated */ +}; + +enum zr_text_align { + ZR_TEXT_LEFT = 0x01, + /* text is aligned on the left (X-axis)*/ + ZR_TEXT_CENTERED = 0x02, + /* text is aligned in the center (X-axis)*/ + ZR_TEXT_RIGHT = 0x04, + /* text is aligned on the right (X-axis)*/ + ZR_TEXT_TOP = 0x08, + /* text is aligned to the top (Y-axis)*/ + ZR_TEXT_MIDDLE = 0x10, + /* text is aligned to the middle (Y-axis) */ + ZR_TEXT_BOTTOM = 0x20 + /* text is aligned to the bottom (Y-axis)*/ +}; + +enum zr_button_behavior { + ZR_BUTTON_DEFAULT, + /* default push button behavior */ + ZR_BUTTON_REPEATER + /* repeater behavior will trigger as long as button is down */ +}; + +enum zr_chart_type { + ZR_CHART_LINES, + /* Line chart with data points connected by lines */ + ZR_CHART_COLUMN, + /* Histogram with value represented as bars */ + ZR_CHART_MAX +}; + +enum zr_chart_event { + ZR_CHART_HOVERING = 0x01, + /* mouse hoveres over current value */ + ZR_CHART_CLICKED = 0x02 + /* mouse click on current value */ +}; + +enum zr_popup_type { + ZR_POPUP_STATIC, + /* static fixed height non growing popup */ + ZR_POPUP_DYNAMIC + /* dynamically growing popup with maximum height */ +}; + +enum zr_layout_format { + ZR_DYNAMIC, /* row layout which scales with the window */ + ZR_STATIC /* row layout with fixed pixel width */ +}; + +enum zr_layout_node_type { + ZR_LAYOUT_NODE, + /* a node is a space which can be minimized or maximized */ + ZR_LAYOUT_TAB + /* a tab is a node with a header */ +}; + +struct zr_chart { + enum zr_chart_type type; + /* chart type with either line or column chart */ + float x, y; + /* chart canvas space position */ + float w, h; + /* chart canvas space size */ + float min, max, range; + /* min and max value for correct scaling of values */ + struct zr_vec2 last; + /* last line chart point to connect to. Only used by the line chart */ + zr_size index; + /* current chart value index*/ + zr_size count; + /* number of values inside the chart */ +}; + +enum zr_row_layout_type { + ZR_LAYOUT_DYNAMIC_FIXED, + /* fixed widget ratio width window layout */ + ZR_LAYOUT_DYNAMIC_ROW, + /* immediate mode widget specific widget width ratio layout */ + ZR_LAYOUT_DYNAMIC_FREE, + /* free ratio based placing of widget in a local space */ + ZR_LAYOUT_DYNAMIC, + /* retain mode widget specific widget ratio width*/ + ZR_LAYOUT_STATIC_FIXED, + /* fixed widget pixel width window layout */ + ZR_LAYOUT_STATIC_ROW, + /* immediate mode widget specific widget pixel width layout */ + ZR_LAYOUT_STATIC_FREE, + /* free pixel based placing of widget in a local space */ + ZR_LAYOUT_STATIC + /* retain mode widget specific widget pixel width layout */ +}; + +struct zr_row_layout { + enum zr_row_layout_type type; + /* type of the row layout */ + zr_size index; + /* index of the current widget in the current window row */ + float height; + /* height of the current row */ + zr_size columns; + /* number of columns in the current row */ + const float *ratio; + /* row widget width ratio */ + float item_width, item_height; + /* current width of very item */ + float item_offset; + /* x positon offset of the current item */ + float filled; + /* total fill ratio */ + struct zr_rect item; + /* item bounds */ + int tree_depth; +}; + +struct zr_menu { + float x, y, w, h; + /* menu bounds */ + struct zr_scroll offset; + /* saved window scrollbar offset */ +}; + +enum zr_window_flags { + ZR_WINDOW_BORDER = ZR_FLAG(0), + /* Draws a border around the window to visually seperate the window + * from the background */ + ZR_WINDOW_BORDER_HEADER = ZR_FLAG(1), + /* Draws a border between window header and body */ + ZR_WINDOW_MOVABLE = ZR_FLAG(2), + /* The moveable flag inidicates that a window can be moved by user input or + * by dragging the window header */ + ZR_WINDOW_SCALABLE = ZR_FLAG(3), + /* The scaleable flag indicates that a window can be scaled by user input + * by dragging a scaler icon at the button of the window */ + ZR_WINDOW_CLOSABLE = ZR_FLAG(4), + /* adds a closeable icon into the header */ + ZR_WINDOW_MINIMIZABLE = ZR_FLAG(5), + /* adds a minimize icon into the header */ + ZR_WINDOW_DYNAMIC = ZR_FLAG(6), + /* special type of window which grows up in height while being filled to a + * certain maximum height. It is mainly used for combo boxes/menus but can + * be used to create perfectly fitting windows as well */ + ZR_WINDOW_NO_SCROLLBAR = ZR_FLAG(7), + /* Removes the scrollbar from the window */ + ZR_WINDOW_TITLE = ZR_FLAG(8) + /* Removes the scrollbar from the window */ +}; + +struct zr_popup_buffer { + zr_size begin; + /* begin of the subbuffer */ + zr_size parent; + /* last entry before the sub buffer*/ + zr_size last; + /* last entry in the sub buffer*/ + zr_size end; + /* end of the subbuffer */ + int active; +}; + +struct zr_panel { + zr_flags flags; + /* window flags modifing its behavior */ + struct zr_rect bounds; + /* position and size of the window in the os window */ + struct zr_scroll *offset; + /* window scrollbar offset */ + float at_x, at_y, max_x; + /* index position of the current widget row and column */ + float width, height; + /* size of the actual useable space inside the window */ + float footer_h; + /* height of the window footer space */ + float header_h; + /* height of the window footer space */ + struct zr_rect clip; + /* window clipping rect */ + struct zr_menu menu; + /* window menubar bounds */ + struct zr_row_layout row; + /* currently used window row layout */ + struct zr_chart chart; + /* chart state */ + struct zr_popup_buffer popup_buffer; + /* output command buffer queuing all popup drawing calls */ + struct zr_command_buffer *buffer; + struct zr_panel *parent; +}; + +/*============================================================== + * CONTEXT + * =============================================================*/ +struct zr_clipboard { + zr_handle userdata; + /* user memory for callback */ + zr_paste_f paste; + /* paste callback for the edit box */ + zr_copy_f copy; + /* copy callback for the edit box */ +}; + +struct zr_canvas { + float global_alpha; + /* alpha modifier for all shapes */ + enum zr_anti_aliasing shape_AA; + /* flag indicating if anti-aliasing should be used to render shapes */ + enum zr_anti_aliasing line_AA; + /* flag indicating if anti-aliasing should be used to render lines */ + struct zr_draw_null_texture null; + /* texture with white pixel for easy primitive drawing */ + struct zr_rect clip_rect; + /* current clipping rectangle */ + struct zr_buffer *buffer; + /* buffer to store draw commands and temporarily store path */ + struct zr_buffer *vertices; + /* buffer to store each draw vertex */ + struct zr_buffer *elements; + /* buffer to store each draw element index */ + unsigned int element_count; + /* total number of elements inside the elements buffer */ + unsigned int vertex_count; + /* total number of vertices inside the vertex buffer */ + zr_size cmd_offset; + /* offset to the first command in the buffer */ + unsigned int cmd_count; + /* number of commands inside the buffer */ + unsigned int path_count; + /* current number of points inside the path */ + unsigned int path_offset; + /* offset to the first point in the buffer */ + struct zr_vec2 circle_vtx[12]; + /* small lookup table for fast circle drawing */ +#if ZR_COMPILE_WITH_COMMAND_USERDATA + zr_handle userdata; +#endif +}; + +struct zr_context { + unsigned int seq; + struct zr_input input; + struct zr_style style; + struct zr_buffer memory; + struct zr_clipboard clip; + void *pool; + +#if ZR_COMPILE_WITH_VERTEX_BUFFER + struct zr_canvas canvas; +#endif +#if ZR_COMPILE_WITH_COMMAND_USERDATA + zr_handle userdata; +#endif + + int build; + struct zr_window *begin; + struct zr_window *end; + struct zr_window *active; + struct zr_window *current; + struct zr_window *freelist; + unsigned int count; +}; + +/*-------------------------------------------------------------- + * CONTEXT + * -------------------------------------------------------------*/ +int zr_init_fixed(struct zr_context*, void *memory, zr_size size, + const struct zr_user_font*); +int zr_init_custom(struct zr_context*, struct zr_buffer *cmds, + struct zr_buffer *pool, const struct zr_user_font*); +int zr_init(struct zr_context*, struct zr_allocator*, + const struct zr_user_font*); +void zr_clear(struct zr_context*); +void zr_free(struct zr_context*); +#if ZR_COMPILE_WITH_COMMAND_USERDATA +void zr_set_user_data(struct zr_context*, zr_handle handle); +#endif + +/* window */ +int zr_begin(struct zr_context*, struct zr_panel*, const char *title, + struct zr_rect bounds, unsigned int flags); +void zr_end(struct zr_context*); + +struct zr_rect zr_window_get_bounds(const struct zr_context*); +struct zr_vec2 zr_window_get_position(const struct zr_context*); +struct zr_vec2 zr_window_get_size(const struct zr_context*); +float zr_window_get_width(const struct zr_context*); +float zr_window_get_height(const struct zr_context*); +struct zr_rect zr_window_get_content_region(struct zr_context*); +struct zr_vec2 zr_window_get_content_region_min(struct zr_context*); +struct zr_vec2 zr_window_get_content_region_max(struct zr_context*); +struct zr_vec2 zr_window_get_content_region_size(struct zr_context*); +struct zr_command_buffer* zr_window_get_canvas(struct zr_context*); +int zr_window_has_focus(const struct zr_context*); +int zr_window_is_collapsed(struct zr_context*, const char *name); +int zr_window_is_closed(struct zr_context*, const char *name); +int zr_window_is_active(struct zr_context*, const char *name); + +void zr_window_set_bounds(struct zr_context*, struct zr_rect); +void zr_window_set_position(struct zr_context*, struct zr_vec2); +void zr_window_set_size(struct zr_context*, struct zr_vec2); +void zr_window_set_focus(struct zr_context *ctx, const char *name); +void zr_window_collapse(struct zr_context *ctx, const char *name, + enum zr_collapse_states); +void zr_window_collapse_if(struct zr_context *ctx, const char *name, + enum zr_collapse_states, int cond); + +/*-------------------------------------------------------------- + * DRAWING + * -------------------------------------------------------------*/ +/* command drawing */ +#define zr_command(t, c) ((const struct zr_command_##t*)c) +#define zr_foreach(c, ctx) for((c)=zr__begin(ctx); (c)!=0; (c)=zr__next(ctx, c)) +const struct zr_command* zr__next(struct zr_context*, const struct zr_command*); +const struct zr_command* zr__begin(struct zr_context*); + +/* vertex command drawing */ +struct zr_convert_config { + float global_alpha; + /* global alpha modifier */ + float line_thickness; + /* line thickness should generally default to 1*/ + enum zr_anti_aliasing line_AA; + /* line anti-aliasing flag can be turned off if you are thight on memory */ + enum zr_anti_aliasing shape_AA; + /* shape anti-aliasing flag can be turned off if you are thight on memory */ + unsigned int circle_segment_count; + /* number of segments used for circle and curves: default to 22 */ + struct zr_draw_null_texture null; + /* handle to texture with a white pixel to draw text */ +}; + +void zr_convert(struct zr_context*, struct zr_buffer *cmds, + struct zr_buffer *vertices, struct zr_buffer *elements, + const struct zr_convert_config*); +#define zr_draw_foreach(cmd,ctx, b)\ + for((cmd)=zr__draw_begin(ctx, b); (cmd)!=0; (cmd)=zr__draw_next(cmd, b, ctx)) +const struct zr_draw_command* zr__draw_begin(const struct zr_context*, + const struct zr_buffer*); +const struct zr_draw_command* zr__draw_next(const struct zr_draw_command*, + const struct zr_buffer*, + const struct zr_context*); + +/*-------------------------------------------------------------- + * INPUT + * -------------------------------------------------------------*/ +void zr_input_begin(struct zr_context*); +void zr_input_motion(struct zr_context*, int x, int y); +void zr_input_key(struct zr_context*, enum zr_keys, int down); +void zr_input_button(struct zr_context*, enum zr_buttons, int x, int y, int down); +void zr_input_scroll(struct zr_context*, float y); +void zr_input_glyph(struct zr_context*, const zr_glyph); +void zr_input_char(struct zr_context*, char); +void zr_input_unicode(struct zr_context *in, zr_rune); +void zr_input_end(struct zr_context*); + +/*-------------------------------------------------------------- + * STYLE + * -------------------------------------------------------------*/ +void zr_load_default_style(struct zr_context*, zr_flags); +void zr_set_font(struct zr_context*, const struct zr_user_font*); +struct zr_vec2 zr_get_property(const struct zr_context*, enum zr_style_properties); +struct zr_color zr_get_color(const struct zr_context*, enum zr_style_colors); +const char *zr_get_color_name(enum zr_style_colors); +const char *zr_get_rounding_name(enum zr_style_rounding); +const char *zr_get_property_name(enum zr_style_properties); + +/* temporarily modify a style value and save the old value in a stack*/ +void zr_push_property(struct zr_context*, enum zr_style_properties, struct zr_vec2); +void zr_push_color(struct zr_context*, enum zr_style_colors, struct zr_color); +void zr_push_font(struct zr_context*, struct zr_user_font font); +void zr_push_font_height(struct zr_context*, float font_height); + +/* restores a previously saved style value */ +void zr_pop_color(struct zr_context*); +void zr_pop_property(struct zr_context*); +void zr_pop_font(struct zr_context*); +void zr_pop_font_height(struct zr_context*); + +/* resets the style back into the beginning state */ +void zr_reset_colors(struct zr_context*); +void zr_reset_properties(struct zr_context*); +void zr_reset_font(struct zr_context*); +void zr_reset_font_height(struct zr_context*); +void zr_reset(struct zr_context*); + +/*-------------------------------------------------------------- + * Layout + * -------------------------------------------------------------*/ +void zr_layout_peek(struct zr_rect *bounds, struct zr_context*); + +/* columns based layouting with generated position and width and fixed height*/ +void zr_layout_row_dynamic(struct zr_context*, float height, zr_size cols); +void zr_layout_row_static(struct zr_context*, float height, zr_size item_width, + zr_size cols); + +/* widget layouting with custom widget width and fixed height */ +void zr_layout_row_begin(struct zr_context*, enum zr_layout_format, + float row_height, zr_size cols); +void zr_layout_row_push(struct zr_context*, float value); +void zr_layout_row_end(struct zr_context*); +void zr_layout_row(struct zr_context*, enum zr_layout_format, float height, + zr_size cols, const float *ratio); + +/* layouting with custom position and size of widgets */ +void zr_layout_space_begin(struct zr_context*, enum zr_layout_format, + float height, zr_size widget_count); +void zr_layout_space_push(struct zr_context*, struct zr_rect); +void zr_layout_space_end(struct zr_context*); + +struct zr_rect zr_layout_space_bounds(struct zr_context*); +struct zr_vec2 zr_layout_space_to_screen(struct zr_context*, struct zr_vec2); +struct zr_vec2 zr_layout_space_to_local(struct zr_context*, struct zr_vec2); +struct zr_rect zr_layout_space_rect_to_screen(struct zr_context*, struct zr_rect); +struct zr_rect zr_layout_space_rect_to_local(struct zr_context*, struct zr_rect); + +/* group layout */ +int zr_group_begin(struct zr_context*, struct zr_panel*, const char *title, zr_flags); +void zr_group_end(struct zr_context *ctx); + +/* tree layout */ +int zr_layout_push(struct zr_context*, enum zr_layout_node_type, const char *title, + enum zr_collapse_states initial_state); +void zr_layout_pop(struct zr_context*); + +/*-------------------------------------------------------------- + * Widgets + * -------------------------------------------------------------*/ +enum zr_widget_state zr_widget(struct zr_rect*, const struct zr_context*); +enum zr_widget_state zr_widget_fitting(struct zr_rect*, struct zr_context*); +void zr_spacing(struct zr_context*, zr_size cols); +void zr_seperator(struct zr_context*); + +/* content output widgets */ +void zr_text(struct zr_context*, const char*, zr_size, enum zr_text_align); +void zr_text_colored(struct zr_context*, const char*, zr_size, enum zr_text_align, + struct zr_color); +void zr_text_wrap(struct zr_context*, const char*, zr_size); +void zr_text_wrap_colored(struct zr_context*, const char*, zr_size, struct zr_color); +void zr_label(struct zr_context*, const char*, enum zr_text_align); +void zr_label_colored(struct zr_context*, const char*, enum zr_text_align, + struct zr_color); +void zr_label_wrap(struct zr_context*, const char*); +void zr_label_colored_wrap(struct zr_context*, const char*, struct zr_color); +void zr_image(struct zr_context*, struct zr_image); + +/* toggle value */ +int zr_check(struct zr_context*, const char*, int active); +int zr_checkbox(struct zr_context*, const char*, int *active); +void zr_radio(struct zr_context*, const char*, int *active); +int zr_option(struct zr_context*, const char*, int active); +int zr_selectable(struct zr_context*, const char*, enum zr_text_align, int *value); +int zr_select(struct zr_context*, const char*, enum zr_text_align, int value); + +/* buttons (push/press) */ +int zr_button_text(struct zr_context*, const char*, enum zr_button_behavior); +int zr_button_color(struct zr_context*, struct zr_color, enum zr_button_behavior); +int zr_button_symbol(struct zr_context*, enum zr_symbol_type, enum zr_button_behavior); +int zr_button_image(struct zr_context*, struct zr_image img, enum zr_button_behavior); +int zr_button_text_symbol(struct zr_context*, enum zr_symbol_type, const char*, + enum zr_text_align, enum zr_button_behavior); +int zr_button_text_image(struct zr_context*, struct zr_image img, const char*, + enum zr_text_align align, enum zr_button_behavior); + +/* simple value modifier by sliding */ +void zr_progress(struct zr_context*, zr_size *cur, zr_size max, int modifyable); +void zr_slider_float(struct zr_context*, float min, float *val, float max, float step); +void zr_slider_int(struct zr_context*, int min, int *val, int max, int step); +float zr_slide_float(struct zr_context*, float min, float val, float max, float step); +int zr_slide_int(struct zr_context*, int min, int val, int max, int step); + +/* extended value modifier by dragging, increment/decrement and text input */ +void zr_property_float(struct zr_context *layout, const char *name, + float min, float *val, float max, float step, + float inc_per_pixel); +void zr_property_int(struct zr_context *layout, const char *name, + int min, int *val, int max, int step, int inc_per_pixel); +float zr_propertyf(struct zr_context *layout, const char *name, float min, + float val, float max, float step, float inc_per_pixel); +int zr_propertyi(struct zr_context *layout, const char *name, int min, int val, + int max, int step, int inc_per_pixel); + +/* text manipulation */ +zr_flags zr_edit_string(struct zr_context*, zr_flags, char *buffer, zr_size *len, + zr_size max, zr_filter); +zr_flags zr_edit_buffer(struct zr_context*, zr_flags, struct zr_buffer*, zr_filter); + +/* simple chart */ +void zr_chart_begin(struct zr_context*, enum zr_chart_type, zr_size num, + float min, float max); +zr_flags zr_chart_push(struct zr_context*, float); +void zr_chart_end(struct zr_context*); + +/*-------------------------------------------------------------- + * Popups + * -------------------------------------------------------------*/ +int zr_popup_begin(struct zr_context*, struct zr_panel*, enum zr_popup_type, + const char *title, zr_flags, struct zr_rect bounds); +void zr_popup_close(struct zr_context*); +void zr_popup_end(struct zr_context*); + +/* abstract combo box */ +int zr_combo_begin_text(struct zr_context*, struct zr_panel*, + const char *selected, int max_height); +int zr_combo_begin_color(struct zr_context*, struct zr_panel*, + struct zr_color color, int max_height); +int zr_combo_begin_image(struct zr_context*, struct zr_panel*, + struct zr_image img, int max_height); +int zr_combo_begin_icon(struct zr_context*, struct zr_panel*, + const char *selected, struct zr_image img, int height); +int zr_combo_item(struct zr_context*, const char*, enum zr_text_align); +int zr_combo_item_icon(struct zr_context*, struct zr_image, const char*, + enum zr_text_align align); +int zr_combo_item_symbol(struct zr_context*, enum zr_symbol_type symbol, + const char*, enum zr_text_align align); +void zr_combo_close(struct zr_context*); +void zr_combo_end(struct zr_context*); + +/* contextual menu */ +int zr_contextual_begin(struct zr_context*, struct zr_panel*, zr_flags flags, + struct zr_vec2 size, struct zr_rect trigger_bounds); +int zr_contextual_item(struct zr_context*, const char*, enum zr_text_align align); +int zr_contextual_item_icon(struct zr_context*, struct zr_image, + const char*, enum zr_text_align); +int zr_contextual_item_symbol(struct zr_context*, enum zr_symbol_type, + const char*, enum zr_text_align); +void zr_contextual_close(struct zr_context*); +void zr_contextual_end(struct zr_context*); + +/* tooltip */ +void zr_tooltip(struct zr_context*, const char *text); +int zr_tooltip_begin(struct zr_context*, struct zr_panel*, float width); +void zr_tooltip_end(struct zr_context*); + +/*-------------------------------------------------------------- + * MENU + * -------------------------------------------------------------*/ +void zr_menubar_begin(struct zr_context*); +void zr_menubar_end(struct zr_context*); + +int zr_menu_text_begin(struct zr_context*, struct zr_panel*, + const char *title, float width); +int zr_menu_icon_begin(struct zr_context*, struct zr_panel*, const char *id, + struct zr_image, float width); +int zr_menu_symbol_begin(struct zr_context*, struct zr_panel*, const char *id, + enum zr_symbol_type, float width); +int zr_menu_item(struct zr_context*, enum zr_text_align align, const char *id); +int zr_menu_item_icon(struct zr_context*, struct zr_image, const char*, + enum zr_text_align); +int zr_menu_item_symbol(struct zr_context*, enum zr_symbol_type, const char *id, + enum zr_text_align); +void zr_menu_close(struct zr_context*); +void zr_menu_end(struct zr_context*); + +#ifdef __cplusplus +} +#endif + +#endif /* ZR_H_ */ + From 76dc51faeba6e8ae2e209f1f995d0e5da181066e Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 14:30:28 -0500 Subject: [PATCH 03/26] re-add POC implementation --- menu/drivers/wimp.c | 2018 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2018 insertions(+) create mode 100644 menu/drivers/wimp.c diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c new file mode 100644 index 0000000000..a2d2734b8e --- /dev/null +++ b/menu/drivers/wimp.c @@ -0,0 +1,2018 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2016 - Daniel De Matteis + * Copyright (C) 2014-2015 - Jean-André Santoni + * + * RetroArch 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch 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 RetroArch. + * If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "menu_generic.h" + +#include "../menu_driver.h" +#include "../menu_animation.h" +#include "../menu_navigation.h" +#include "../menu_hash.h" +#include "../menu_display.h" + +#include "../../core_info.h" +#include "../../configuration.h" +#include "../../frontend/frontend_driver.h" +#include "../../system.h" +#include "../../runloop.h" +#include "../../verbosity.h" +#include "../../tasks/tasks_internal.h" + +#include "../../gfx/common/gl_common.h" +#include +#include +#include +#include +#include "../../deps/zahnrad/zahnrad.h" + +#define MAX_VERTEX_MEMORY 512 * 1024 +#define MAX_ELEMENT_MEMORY 128 * 1024 + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) < (b) ? (b) : (a)) +#endif +#ifndef CLAMP +#define CLAMP(i,v,x) (MAX(MIN(v,x), i)) +#endif +#define LEN(a) (sizeof(a)/sizeof(a)[0]) +#define UNUSED(a) ((void)(a)) + +#define MAX_BUFFER 64 +#define MAX_MEMORY (32 * 1024) +#define MAX_COMMAND_MEMORY (16 * 1024) +#define WINDOW_WIDTH 1200 +#define WINDOW_HEIGHT 800 + + +enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; + +struct demo { + void *memory; + struct zr_context ctx; + enum theme theme; + struct zr_memory_status status; +}; +static void +simple_window(struct zr_context *ctx) +{ + /* simple demo window */ + struct zr_panel layout; + if (zr_begin(ctx, &layout, "Show", zr_rect(100, 100, 200, 200), + ZR_WINDOW_BORDER|ZR_WINDOW_MOVABLE|ZR_WINDOW_SCALABLE| + ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_TITLE)) + { + enum {EASY, HARD}; + static int op = EASY; + static int property = 20; + + zr_layout_row_static(ctx, 30, 80, 1); + if (zr_button_text(ctx, "button", ZR_BUTTON_DEFAULT)) { + /* event handling */ + } + zr_layout_row_dynamic(ctx, 30, 2); + if (zr_option(ctx, "easy", op == EASY)) op = EASY; + if (zr_option(ctx, "hard", op == HARD)) op = HARD; + + zr_layout_row_dynamic(ctx, 22, 1); + zr_property_int(ctx, "Compression:", 0, &property, 100, 10, 1); + } + zr_end(ctx); +} + +static int +run_demo(struct demo *gui) +{ + int ret = 1; + static int init = 0; + struct zr_context *ctx = &gui->ctx; + + if (!init) { + init = 1; + } + + /* windows */ + simple_window(ctx); + +// ret = control_window(ctx, gui); + zr_buffer_info(&gui->status, &gui->ctx.memory); + return ret; +} + +static struct demo gui; +/* ============================================================== + * + * Utility + * + * ===============================================================*/ +static void +die(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fputs("\n", stderr); + exit(EXIT_FAILURE); +} + +static char* +file_load(const char* path, size_t* siz) +{ + char *buf; + FILE *fd = fopen(path, "rb"); + if (!fd) die("Failed to open file: %s\n", path); + fseek(fd, 0, SEEK_END); + *siz = (size_t)ftell(fd); + fseek(fd, 0, SEEK_SET); + buf = (char*)calloc(*siz, 1); + fread(buf, *siz, 1, fd); + fclose(fd); + return buf; +} + +struct device { + struct zr_buffer cmds; + struct zr_draw_null_texture null; + GLuint vbo, vao, ebo; + + GLuint prog; + GLuint vert_shdr; + GLuint frag_shdr; + + GLint attrib_pos; + GLint attrib_uv; + GLint attrib_col; + + GLint uniform_tex; + GLint uniform_proj; + GLuint font_tex; +}; + +static void +device_init(struct device *dev) +{ + GLint status; + static const GLchar *vertex_shader = + "#version 300 es\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 TexCoord;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = TexCoord;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n"; + static const GLchar *fragment_shader = + "#version 300 es\n" + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main(){\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; + + dev->prog = glCreateProgram(); + dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER); + dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0); + glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0); + glCompileShader(dev->vert_shdr); + glCompileShader(dev->frag_shdr); + glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status); + assert(status == GL_TRUE); + glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status); + assert(status == GL_TRUE); + glAttachShader(dev->prog, dev->vert_shdr); + glAttachShader(dev->prog, dev->frag_shdr); + glLinkProgram(dev->prog); + glGetProgramiv(dev->prog, GL_LINK_STATUS, &status); + assert(status == GL_TRUE); + + dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture"); + dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx"); + dev->attrib_pos = glGetAttribLocation(dev->prog, "Position"); + dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord"); + dev->attrib_col = glGetAttribLocation(dev->prog, "Color"); + + { + /* buffer setup */ + GLsizei vs = sizeof(struct zr_draw_vertex); + size_t vp = offsetof(struct zr_draw_vertex, position); + size_t vt = offsetof(struct zr_draw_vertex, uv); + size_t vc = offsetof(struct zr_draw_vertex, col); + + glGenBuffers(1, &dev->vbo); + glGenBuffers(1, &dev->ebo); + glGenVertexArrays(1, &dev->vao); + + glBindVertexArray(dev->vao); + glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); + + glEnableVertexAttribArray((GLuint)dev->attrib_pos); + glEnableVertexAttribArray((GLuint)dev->attrib_uv); + glEnableVertexAttribArray((GLuint)dev->attrib_col); + + glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp); + glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt); + glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +static struct zr_user_font +font_bake_and_upload(struct device *dev, struct zr_font *font, + const char *path, unsigned int font_height, const zr_rune *range) +{ + int glyph_count; + int img_width, img_height; + struct zr_font_glyph *glyphes; + struct zr_baked_font baked_font; + struct zr_user_font user_font; + struct zr_recti custom; + + memset(&baked_font, 0, sizeof(baked_font)); + memset(&user_font, 0, sizeof(user_font)); + memset(&custom, 0, sizeof(custom)); + + { + /* bake and upload font texture */ + void *img, *tmp; + size_t ttf_size; + size_t tmp_size, img_size; + const char *custom_data = "...."; + struct zr_font_config config; + char *ttf_blob = file_load(path, &ttf_size); + if (!ttf_blob) + die("[Font]: %s is not a file or cannot be found!\n", path); + + /* setup font configuration */ + memset(&config, 0, sizeof(config)); + config.ttf_blob = ttf_blob; + config.ttf_size = ttf_size; + config.font = &baked_font; + config.coord_type = ZR_COORD_UV; + config.range = range; + config.pixel_snap = zr_false; + config.size = (float)font_height; + config.spacing = zr_vec2(0,0); + config.oversample_h = 1; + config.oversample_v = 1; + + /* query needed amount of memory for the font baking process */ + zr_font_bake_memory(&tmp_size, &glyph_count, &config, 1); + glyphes = (struct zr_font_glyph*)calloc(sizeof(struct zr_font_glyph), (size_t)glyph_count); + tmp = calloc(1, tmp_size); + + /* pack all glyphes and return needed image width, height and memory size*/ + custom.w = 2; custom.h = 2; + if (!zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1)) + die("[Font]: failed to load font!\n"); + + /* bake all glyphes and custom white pixel into image */ + img = calloc(1, img_size); + zr_font_bake(img, img_width, img_height, tmp, tmp_size, glyphes, glyph_count, &config, 1); + zr_font_bake_custom_data(img, img_width, img_height, custom, custom_data, 2, 2, '.', 'X'); + { + /* convert alpha8 image into rgba8 image */ + void *img_rgba = calloc(4, (size_t)(img_height * img_width)); + zr_font_bake_convert(img_rgba, img_width, img_height, img); + free(img); + img = img_rgba; + } + { + /* upload baked font image */ + glGenTextures(1, &dev->font_tex); + glBindTexture(GL_TEXTURE_2D, dev->font_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)img_width, (GLsizei)img_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, img); + } + free(ttf_blob); + free(tmp); + free(img); + } + + /* default white pixel in a texture which is needed to draw primitives */ + dev->null.texture.id = (int)dev->font_tex; + dev->null.uv = zr_vec2((custom.x + 0.5f)/(float)img_width, + (custom.y + 0.5f)/(float)img_height); + + /* setup font with glyphes. IMPORTANT: the font only references the glyphes + this was done to have the possibility to have multible fonts with one + total glyph array. Not quite sure if it is a good thing since the + glyphes have to be freed as well. */ + zr_font_init(font, (float)font_height, '?', glyphes, &baked_font, dev->null.texture); + user_font = zr_font_ref(font); + return user_font; +} + +static void +device_shutdown(struct device *dev) +{ + glDetachShader(dev->prog, dev->vert_shdr); + glDetachShader(dev->prog, dev->frag_shdr); + glDeleteShader(dev->vert_shdr); + glDeleteShader(dev->frag_shdr); + glDeleteProgram(dev->prog); + glDeleteTextures(1, &dev->font_tex); + glDeleteBuffers(1, &dev->vbo); + glDeleteBuffers(1, &dev->ebo); +} + +static void +device_draw(struct device *dev, struct zr_context *ctx, int width, int height, + enum zr_anti_aliasing AA) +{ + GLint last_prog, last_tex; + GLint last_ebo, last_vbo, last_vao; + GLfloat ortho[4][4] = { + {2.0f, 0.0f, 0.0f, 0.0f}, + {0.0f,-2.0f, 0.0f, 0.0f}, + {0.0f, 0.0f,-1.0f, 0.0f}, + {-1.0f,1.0f, 0.0f, 1.0f}, + }; + ortho[0][0] /= (GLfloat)width; + ortho[1][1] /= (GLfloat)height; + + /* save previous opengl state */ + glGetIntegerv(GL_CURRENT_PROGRAM, &last_prog); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_vao); + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_ebo); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vbo); + + /* setup global state */ + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glActiveTexture(GL_TEXTURE0); + + /* setup program */ + glUseProgram(dev->prog); + glUniform1i(dev->uniform_tex, 0); + glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]); + + { + /* convert from command queue into draw list and draw to screen */ + const struct zr_draw_command *cmd; + void *vertices, *elements; + const zr_draw_index *offset = NULL; + + /* allocate vertex and element buffer */ + glBindVertexArray(dev->vao); + glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); + + glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW); + + /* load draw vertices & elements directly into vertex + element buffer */ + vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); + elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); + { + struct zr_buffer vbuf, ebuf; + + /* fill converting configuration */ + struct zr_convert_config config; + memset(&config, 0, sizeof(config)); + config.global_alpha = 1.0f; + config.shape_AA = AA; + config.line_AA = AA; + config.circle_segment_count = 22; + config.line_thickness = 1.0f; + config.null = dev->null; + + /* setup buffers to load vertices and elements */ + zr_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY); + zr_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY); + zr_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config); + } + glUnmapBuffer(GL_ARRAY_BUFFER); + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + + /* iterate over and execute each draw command */ + zr_draw_foreach(cmd, ctx, &dev->cmds) { + if (!cmd->elem_count) continue; + glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); + glScissor((GLint)cmd->clip_rect.x, + height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h), + (GLint)cmd->clip_rect.w, (GLint)cmd->clip_rect.h); + glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); + offset += cmd->elem_count; + } + zr_clear(ctx); + } + + /* restore old state */ + glUseProgram((GLuint)last_prog); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex); + glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)last_ebo); + glBindVertexArray((GLuint)last_vao); + glDisable(GL_SCISSOR_TEST); +} + +static void +error_callback(int error, const char *description) +{ + fprintf(stderr, "Error %d: %s\n", error, description); +} + +static void* mem_alloc(zr_handle unused, size_t size) +{UNUSED(unused); return calloc(1, size);} +static void mem_free(zr_handle unused, void *ptr) +{UNUSED(unused); free(ptr);} + +struct device device; +struct zr_font font; +char font_path[PATH_MAX_LENGTH]; +int width = 0, height = 0; + +struct zr_user_font usrfnt; +struct zr_allocator alloc; + +void init() +{ + settings_t *settings = config_get_ptr(); + fill_pathname_join(font_path, settings->assets_directory, + "wimp", sizeof(font_path)); + printf("Font: \n%s\n\n\n",settings->assets_directory); + fill_pathname_join(font_path, font_path, + "Roboto-Regular.ttf", sizeof(font_path)); + printf("Font: %s\n\n\n\n",font_path); +} + +void zdraw(int width, int height) +{ + /* OpenGL */ + glViewport(0, 0, width, height); + alloc.userdata.ptr = NULL; + alloc.alloc = mem_alloc; + alloc.free = mem_free; + zr_buffer_init(&device.cmds, &alloc, 1024); + usrfnt = font_bake_and_upload(&device, &font, font_path, 14, + zr_font_default_glyph_ranges()); + zr_init(&gui.ctx, &alloc, &usrfnt); + device_init(&device); + run_demo(&gui); + glViewport(0, 0, width, height); + device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); +} + +enum +{ + wimp_TEXTURE_POINTER = 0, + wimp_TEXTURE_BACK, + wimp_TEXTURE_SWITCH_ON, + wimp_TEXTURE_SWITCH_OFF, + wimp_TEXTURE_TAB_MAIN_ACTIVE, + wimp_TEXTURE_TAB_PLAYLISTS_ACTIVE, + wimp_TEXTURE_TAB_SETTINGS_ACTIVE, + wimp_TEXTURE_TAB_MAIN_PASSIVE, + wimp_TEXTURE_TAB_PLAYLISTS_PASSIVE, + wimp_TEXTURE_TAB_SETTINGS_PASSIVE, + wimp_TEXTURE_LAST +}; + +enum +{ + wimp_SYSTEM_TAB_MAIN = 0, + wimp_SYSTEM_TAB_PLAYLISTS, + wimp_SYSTEM_TAB_SETTINGS +}; + +#define wimp_SYSTEM_TAB_END wimp_SYSTEM_TAB_SETTINGS + +struct wimp_texture_item +{ + uintptr_t id; +}; + +typedef struct wimp_handle +{ + unsigned tabs_height; + unsigned line_height; + unsigned shadow_height; + unsigned scrollbar_width; + unsigned icon_size; + unsigned margin; + unsigned glyph_width; + char box_message[PATH_MAX_LENGTH]; + + struct + { + struct + { + float alpha; + } arrow; + + struct wimp_texture_item bg; + struct wimp_texture_item list[wimp_TEXTURE_LAST]; + uintptr_t white; + } textures; + + struct + { + struct + { + unsigned idx; + unsigned idx_old; + } active; + + float x_pos; + size_t selection_ptr_old; + size_t selection_ptr; + } categories; + + gfx_font_raster_block_t list_block; + float scroll_y; +} wimp_handle_t; + +static void wimp_context_reset_textures(wimp_handle_t *wimp, + const char *iconpath) +{ + unsigned i; + + for (i = 0; i < wimp_TEXTURE_LAST; i++) + { + struct texture_image ti = {0}; + char path[PATH_MAX_LENGTH] = {0}; + + switch(i) + { + case wimp_TEXTURE_POINTER: + fill_pathname_join(path, iconpath, + "pointer.png", sizeof(path)); + break; + case wimp_TEXTURE_BACK: + fill_pathname_join(path, iconpath, + "back.png", sizeof(path)); + break; + case wimp_TEXTURE_SWITCH_ON: + fill_pathname_join(path, iconpath, + "on.png", sizeof(path)); + break; + case wimp_TEXTURE_SWITCH_OFF: + fill_pathname_join(path, iconpath, + "off.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_MAIN_ACTIVE: + fill_pathname_join(path, iconpath, + "main_tab_active.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_PLAYLISTS_ACTIVE: + fill_pathname_join(path, iconpath, + "playlists_tab_active.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_SETTINGS_ACTIVE: + fill_pathname_join(path, iconpath, + "settings_tab_active.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_MAIN_PASSIVE: + fill_pathname_join(path, iconpath, + "main_tab_passive.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_PLAYLISTS_PASSIVE: + fill_pathname_join(path, iconpath, + "playlists_tab_passive.png", sizeof(path)); + break; + case wimp_TEXTURE_TAB_SETTINGS_PASSIVE: + fill_pathname_join(path, iconpath, + "settings_tab_passive.png", sizeof(path)); + break; + } + + if (string_is_empty(path) || !path_file_exists(path)) + continue; + + video_texture_image_load(&ti, path); + video_driver_texture_load(&ti, + TEXTURE_FILTER_MIPMAP_LINEAR, (unsigned*)&wimp->textures.list[i].id); + + video_texture_image_free(&ti); + } +} + +static void wimp_draw_icon(wimp_handle_t *wimp, + uintptr_t texture, + float x, float y, + unsigned width, unsigned height, + float rotation, float scale_factor, + float *color) +{ + menu_display_ctx_rotate_draw_t rotate_draw; + menu_display_ctx_draw_t draw; + struct gfx_coords coords; + math_matrix_4x4 mymat; + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_BEGIN, NULL); + + rotate_draw.matrix = &mymat; + rotate_draw.rotation = rotation; + rotate_draw.scale_x = scale_factor; + rotate_draw.scale_y = scale_factor; + rotate_draw.scale_z = 1; + rotate_draw.scale_enable = true; + + menu_display_ctl(MENU_DISPLAY_CTL_ROTATE_Z, &rotate_draw); + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = (const float*)color; + + draw.x = x; + draw.y = height - y - wimp->icon_size; + draw.width = wimp->icon_size; + draw.height = wimp->icon_size; + draw.coords = &coords; + draw.matrix_data = &mymat; + draw.texture = texture; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW, &draw); + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_END, NULL); +} + + +static void wimp_draw_tab(wimp_handle_t *wimp, + unsigned i, + unsigned width, unsigned height, + float *pure_white) +{ + unsigned tab_icon; + switch (i) + { + case wimp_SYSTEM_TAB_MAIN: + tab_icon = (i == wimp->categories.selection_ptr) + ? wimp_TEXTURE_TAB_MAIN_ACTIVE + : wimp_TEXTURE_TAB_MAIN_PASSIVE; + break; + case wimp_SYSTEM_TAB_PLAYLISTS: + tab_icon = (i == wimp->categories.selection_ptr) + ? wimp_TEXTURE_TAB_PLAYLISTS_ACTIVE + : wimp_TEXTURE_TAB_PLAYLISTS_PASSIVE; + break; + case wimp_SYSTEM_TAB_SETTINGS: + tab_icon = (i == wimp->categories.selection_ptr) + ? wimp_TEXTURE_TAB_SETTINGS_ACTIVE + : wimp_TEXTURE_TAB_SETTINGS_PASSIVE; + break; + } + + wimp_draw_icon(wimp, wimp->textures.list[tab_icon].id, + width / (wimp_SYSTEM_TAB_END+1) * (i+0.5) - wimp->icon_size/2, + height - wimp->tabs_height, + width, height, 0, 1, &pure_white[0]); +} + +static void wimp_blit_line(float x, float y, unsigned width, unsigned height, + const char *message, uint32_t color, enum text_alignment text_align) +{ + int font_size; + struct font_params params; + void *fb_buf = NULL; + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_SIZE, &font_size); + + params.x = x / width; + params.y = 1.0f - (y + font_size / 3) / height; + params.scale = 1.0f; + params.drop_mod = 0.0f; + params.drop_x = 0.0f; + params.drop_y = 0.0f; + params.color = color; + params.full_screen = true; + params.text_align = text_align; + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_BUF, &fb_buf); + + video_driver_set_osd_msg(message, ¶ms, fb_buf); +} + +static void wimp_render_quad(wimp_handle_t *wimp, + int x, int y, unsigned w, unsigned h, + unsigned width, unsigned height, + float *coord_color) +{ + menu_display_ctx_draw_t draw; + struct gfx_coords coords; + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = coord_color; + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_BEGIN, NULL); + + draw.x = x; + draw.y = (int)height - y - (int)h; + draw.width = w; + draw.height = h; + draw.coords = &coords; + draw.matrix_data = NULL; + draw.texture = wimp->textures.white; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW, &draw); + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_END, NULL); +} + +static void wimp_draw_tab_begin(wimp_handle_t *wimp, + unsigned width, unsigned height, + float *white_bg, float *grey_bg) +{ + float scale_factor; + menu_display_ctl(MENU_DISPLAY_CTL_GET_DPI, &scale_factor); + + wimp->tabs_height = scale_factor / 3; + + /* tabs background */ + wimp_render_quad(wimp, 0, height - wimp->tabs_height, width, + wimp->tabs_height, + width, height, + white_bg); + + /* tabs separator */ + wimp_render_quad(wimp, 0, height - wimp->tabs_height, width, + 1, + width, height, + grey_bg); +} + +static void wimp_draw_tab_end(wimp_handle_t *wimp, + unsigned width, unsigned height, + unsigned header_height, + float *blue_bg) +{ + /* active tab marker */ + unsigned tab_width = width / (wimp_SYSTEM_TAB_END+1); + + wimp_render_quad(wimp, wimp->categories.selection_ptr * tab_width, + height - (header_height/16), + tab_width, + header_height/16, + width, height, + &blue_bg[0]); +} + +static void wimp_draw_scrollbar(wimp_handle_t *wimp, + unsigned width, unsigned height, float *coord_color) +{ + unsigned header_height; + float content_height, total_height, + scrollbar_height, scrollbar_margin, y; + + if (!wimp) + return; + + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + content_height = menu_entries_get_end() * wimp->line_height; + total_height = height - header_height - wimp->tabs_height; + scrollbar_margin = wimp->scrollbar_width; + scrollbar_height = total_height / (content_height / total_height); + y = total_height * wimp->scroll_y / content_height; + + /* apply a margin on the top and bottom of the scrollbar for aestetic */ + scrollbar_height -= scrollbar_margin * 2; + y += scrollbar_margin; + + if (content_height >= total_height) + { + /* if the scrollbar is extremely short, display it as a square */ + if (scrollbar_height <= wimp->scrollbar_width) + scrollbar_height = wimp->scrollbar_width; + + wimp_render_quad(wimp, + width - wimp->scrollbar_width - scrollbar_margin, + header_height + y, + wimp->scrollbar_width, + scrollbar_height, + width, height, + coord_color); + } +} + +static void wimp_get_message(void *data, const char *message) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp || !message || !*message) + return; + + strlcpy(wimp->box_message, message, sizeof(wimp->box_message)); +} + +static void wimp_render_messagebox(const char *message) +{ + unsigned i, width, height; + uint32_t normal_color; + int x, y, font_size; + settings_t *settings = config_get_ptr(); + struct string_list *list = (struct string_list*) + string_split(message, "\n"); + + if (!list) + return; + if (list->elems == 0) + goto end; + + video_driver_get_size(&width, &height); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_SIZE, &font_size); + + x = width / 2; + y = height / 2 - list->size * font_size / 2; + + normal_color = FONT_COLOR_ARGB_TO_RGBA(settings->menu.entry_normal_color); + + for (i = 0; i < list->size; i++) + { + const char *msg = list->elems[i].data; + if (msg) + wimp_blit_line(x, y + i * font_size, + width, height, + msg, normal_color, TEXT_ALIGN_CENTER); + } + +end: + string_list_free(list); +} + +static void wimp_render(void *data) +{ + size_t i = 0; + float delta_time, dt; + unsigned bottom, width, height, header_height; + wimp_handle_t *wimp = (wimp_handle_t*)data; + settings_t *settings = config_get_ptr(); + + if (!wimp) + return; + + video_driver_get_size(&width, &height); + + menu_animation_ctl(MENU_ANIMATION_CTL_DELTA_TIME, &delta_time); + dt = delta_time / IDEAL_DT; + menu_animation_ctl(MENU_ANIMATION_CTL_UPDATE, &dt); + + menu_display_ctl(MENU_DISPLAY_CTL_SET_WIDTH, &width); + menu_display_ctl(MENU_DISPLAY_CTL_SET_HEIGHT, &height); + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + if (settings->menu.pointer.enable) + { + int16_t pointer_y = menu_input_pointer_state(MENU_POINTER_Y_AXIS); + float old_accel_val, new_accel_val; + unsigned new_pointer_val = + (pointer_y - wimp->line_height + wimp->scroll_y - 16) + / wimp->line_height; + + menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_READ, &old_accel_val); + menu_input_ctl(MENU_INPUT_CTL_POINTER_PTR, &new_pointer_val); + + wimp->scroll_y -= old_accel_val / 60.0; + + new_accel_val = old_accel_val * 0.96; + + menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_WRITE, &new_accel_val); + } + + if (settings->menu.mouse.enable) + { + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + + unsigned new_pointer_val = + (mouse_y - wimp->line_height + wimp->scroll_y - 16) + / wimp->line_height; + + menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &new_pointer_val); + } + + if (wimp->scroll_y < 0) + wimp->scroll_y = 0; + + bottom = menu_entries_get_end() * wimp->line_height + - height + header_height + wimp->tabs_height; + if (wimp->scroll_y > bottom) + wimp->scroll_y = bottom; + + if (menu_entries_get_end() * wimp->line_height + < height - header_height - wimp->tabs_height) + wimp->scroll_y = 0; + + if (menu_entries_get_end() < height / wimp->line_height) { } + else + i = wimp->scroll_y / wimp->line_height; + + menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i); +} + +static void wimp_render_label_value(wimp_handle_t *wimp, + int y, unsigned width, unsigned height, + uint64_t index, uint32_t color, bool selected, const char *label, + const char *value, float *pure_white) +{ + char label_str[PATH_MAX_LENGTH]; + char value_str[PATH_MAX_LENGTH]; + int value_len = strlen(value); + int ticker_limit = 0; + size_t usable_width = 0; + uintptr_t texture_switch = 0; + bool do_draw_text = false; + uint32_t hash_value = 0; + + usable_width = width - (wimp->margin * 2); + + if (value_len * wimp->glyph_width > usable_width / 2) + value_len = (usable_width/2) / wimp->glyph_width; + + ticker_limit = (usable_width / wimp->glyph_width) - (value_len + 2); + + menu_animation_ticker_str(label_str, ticker_limit, index, label, selected); + menu_animation_ticker_str(value_str, value_len, index, value, selected); + + wimp_blit_line(wimp->margin, y + wimp->line_height / 2, + width, height, label_str, color, TEXT_ALIGN_LEFT); + + hash_value = menu_hash_calculate(value); + + if (string_is_equal(value, "disabled") || string_is_equal(value, "off")) + { + if (wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id; + else + do_draw_text = true; + } + else if (string_is_equal(value, "enabled") || string_is_equal(value, "on")) + { + if (wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id; + else + do_draw_text = true; + } + else + { + switch (hash_value) + { + case MENU_VALUE_COMP: + break; + case MENU_VALUE_MORE: + break; + case MENU_VALUE_CORE: + break; + case MENU_VALUE_RDB: + break; + case MENU_VALUE_CURSOR: + break; + case MENU_VALUE_FILE: + break; + case MENU_VALUE_DIR: + break; + case MENU_VALUE_MUSIC: + break; + case MENU_VALUE_IMAGE: + break; + case MENU_VALUE_MOVIE: + break; + case MENU_VALUE_ON: + if (wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_ON].id; + else + do_draw_text = true; + break; + case MENU_VALUE_OFF: + if (wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id) + texture_switch = wimp->textures.list[wimp_TEXTURE_SWITCH_OFF].id; + else + do_draw_text = true; + break; + default: + do_draw_text = true; + break; + } + } + + if (do_draw_text) + wimp_blit_line(width - wimp->margin, + y + wimp->line_height / 2, + width, height, value_str, color, TEXT_ALIGN_RIGHT); + + if (texture_switch) + wimp_draw_icon(wimp, texture_switch, + width - wimp->margin - wimp->icon_size, y, + width, height, 0, 1, &pure_white[0]); +} + +static void wimp_render_menu_list(wimp_handle_t *wimp, + unsigned width, unsigned height, + uint32_t normal_color, + uint32_t hover_color, + float *pure_white) +{ + unsigned header_height; + uint64_t *frame_count; + size_t i = 0; + size_t end = menu_entries_get_end(); + video_driver_ctl(RARCH_DISPLAY_CTL_GET_FRAME_COUNT, &frame_count); + + if (!menu_display_ctl(MENU_DISPLAY_CTL_UPDATE_PENDING, NULL)) + return; + + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + wimp->list_block.carr.coords.vertices = 0; + + menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i); + + for (; i < end; i++) + { + int y; + size_t selection; + bool entry_selected; + menu_entry_t entry; + + if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) + continue; + + y = header_height - wimp->scroll_y + (wimp->line_height * i); + + if ((y - (int)wimp->line_height) > (int)height + || ((y + (int)wimp->line_height) < 0)) + continue; + + menu_entry_get(&entry, 0, i, NULL, true); + + entry_selected = selection == i; + + wimp_render_label_value(wimp, y, width, height, *frame_count / 20, + entry_selected ? hover_color : normal_color, entry_selected, + entry.path, entry.value, pure_white); + } +} + +static void wimp_draw_cursor(wimp_handle_t *wimp, + float *color, + float x, float y, unsigned width, unsigned height) +{ + menu_display_ctx_draw_t draw; + struct gfx_coords coords; + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = (const float*)color; + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_BEGIN, NULL); + + draw.x = x - 32; + draw.y = (int)height - y - 32; + draw.width = 64; + draw.height = 64; + draw.coords = &coords; + draw.matrix_data = NULL; + draw.texture = wimp->textures.list[wimp_TEXTURE_POINTER].id; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW, &draw); + + menu_display_ctl(MENU_DISPLAY_CTL_BLEND_END, NULL); +} + +static size_t wimp_list_get_size(void *data, menu_list_type_t type) +{ + size_t list_size = 0; + (void)data; + + switch (type) + { + case MENU_LIST_PLAIN: + list_size = menu_entries_get_stack_size(0); + break; + case MENU_LIST_TABS: + list_size = wimp_SYSTEM_TAB_END; + break; + default: + break; + } + + return list_size; +} + +static void bgcolor_setalpha(float *bg, float alpha) +{ + bg[3] = alpha; + bg[7] = alpha; + bg[11] = alpha; + bg[15] = alpha; +} + +static int wimp_get_core_title(char *s, size_t len) +{ + struct retro_system_info *system = NULL; + rarch_system_info_t *info = NULL; + settings_t *settings = config_get_ptr(); + const char *core_name = NULL; + const char *core_version = NULL; + + menu_driver_ctl(RARCH_MENU_CTL_SYSTEM_INFO_GET, + &system); + + core_name = system->library_name; + core_version = system->library_version; + + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); + + if (!settings->menu.core_enable) + return -1; + + if (string_is_empty(core_name)) + core_name = info->info.library_name; + if (string_is_empty(core_name)) + core_name = menu_hash_to_str(MENU_VALUE_NO_CORE); + + if (!core_version) + core_version = info->info.library_version; + if (!core_version) + core_version = ""; + + snprintf(s, len, "%s %s", core_name, core_version); + + return 0; +} + +static void wimp_frame(void *data) +{ + unsigned header_height; + bool display_kb; + float black_bg[16] = { + 0, 0, 0, 0.75, + 0, 0, 0, 0.75, + 0, 0, 0, 0.75, + 0, 0, 0, 0.75, + }; + float blue_bg[16] = { + 0.13, 0.59, 0.95, 1, + 0.13, 0.59, 0.95, 1, + 0.13, 0.59, 0.95, 1, + 0.13, 0.59, 0.95, 1, + }; + float lightblue_bg[16] = { + 0.89, 0.95, 0.99, 1.00, + 0.89, 0.95, 0.99, 1.00, + 0.89, 0.95, 0.99, 1.00, + 0.89, 0.95, 0.99, 1.00, + }; + float pure_white[16]= { + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 1, 1, + }; + float white_bg[16]= { + 0.98, 0.98, 0.98, 1, + 0.98, 0.98, 0.98, 1, + 0.98, 0.98, 0.98, 1, + 0.98, 0.98, 0.98, 1, + }; + float white_transp_bg[16]= { + 0.98, 0.98, 0.98, 0.90, + 0.98, 0.98, 0.98, 0.90, + 0.98, 0.98, 0.98, 0.90, + 0.98, 0.98, 0.98, 0.90, + }; + float grey_bg[16]= { + 0.78, 0.78, 0.78, 1, + 0.78, 0.78, 0.78, 1, + 0.78, 0.78, 0.78, 1, + 0.78, 0.78, 0.78, 1, + }; + float shadow_bg[16]= { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0.2, + 0, 0, 0, 0.2, + }; + unsigned width, height, ticker_limit, i; + char msg[256]; + char title[256]; + char title_buf[256]; + char title_msg[256]; + size_t selection; + size_t title_margin; + uint64_t *frame_count; + menu_display_ctx_draw_t draw; + wimp_handle_t *wimp = (wimp_handle_t*)data; + settings_t *settings = config_get_ptr(); + const uint32_t normal_color = 0x212121ff; + const uint32_t hover_color = 0x212121ff; + const uint32_t title_color = 0xffffffff; + const uint32_t activetab_color = 0x0096f2ff; + const uint32_t passivetab_color = 0x9e9e9eff; + bool background_rendered = false; + bool libretro_running = menu_display_ctl( + MENU_DISPLAY_CTL_LIBRETRO_RUNNING, NULL); + + video_driver_ctl(RARCH_DISPLAY_CTL_GET_FRAME_COUNT, &frame_count); + + (void)passivetab_color; + (void)activetab_color; + + if (!wimp) + return; + + video_driver_get_size(&width, &height); + + menu_display_ctl(MENU_DISPLAY_CTL_SET_VIEWPORT, NULL); + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + if (libretro_running) + { + memset(&draw, 0, sizeof(menu_display_ctx_draw_t)); + + draw.width = width; + draw.height = height; + draw.texture = wimp->textures.white; + draw.handle_alpha = 0.75f; + draw.force_transparency = false; + draw.color = &white_transp_bg[0]; + draw.color2 = &white_bg[0]; + draw.vertex = NULL; + draw.tex_coord = NULL; + draw.vertex_count = 4; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW_BG, &draw); + } + else + { + menu_display_ctx_clearcolor_t clearcolor; + + clearcolor.r = 1.0f; + clearcolor.g = 1.0f; + clearcolor.b = 1.0f; + clearcolor.a = 0.75f; + + menu_display_ctl(MENU_DISPLAY_CTL_CLEAR_COLOR, &clearcolor); + + if (wimp->textures.bg.id) + { + background_rendered = true; + + /* Set new opacity for transposed white background */ + bgcolor_setalpha(white_transp_bg, 0.30); + + memset(&draw, 0, sizeof(menu_display_ctx_draw_t)); + + draw.width = width; + draw.height = height; + draw.texture = wimp->textures.bg.id; + draw.handle_alpha = 0.75f; + draw.force_transparency = true; + draw.color = &white_transp_bg[0]; + draw.color2 = &white_bg[0]; + draw.vertex = NULL; + draw.tex_coord = NULL; + draw.vertex_count = 4; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + + menu_display_ctl(MENU_DISPLAY_CTL_DRAW_BG, &draw); + + /* Restore opacity of transposed white background */ + bgcolor_setalpha(white_transp_bg, 0.90); + } + } + + zdraw(width, height); + + menu_entries_get_title(title, sizeof(title)); + + if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) + return; + + if (background_rendered || libretro_running) + bgcolor_setalpha(lightblue_bg, 0.75); + else + bgcolor_setalpha(lightblue_bg, 1.0); + + /* highlighted entry */ + wimp_render_quad(wimp, 0, + header_height - wimp->scroll_y + wimp->line_height * + selection, width, wimp->line_height, + width, height, + &lightblue_bg[0]); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_BIND_BLOCK, &wimp->list_block); + + wimp_render_menu_list(wimp, width, height, + normal_color, hover_color, &pure_white[0]); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_FLUSH_BLOCK, NULL); + menu_animation_ctl(MENU_ANIMATION_CTL_SET_ACTIVE, NULL); + + /* header */ + wimp_render_quad(wimp, 0, 0, width, header_height, + width, height, &blue_bg[0]); + + wimp->tabs_height = 0; + + /* display tabs if depth equal one, if not hide them */ + if (wimp_list_get_size(wimp, MENU_LIST_PLAIN) == 1) + { + wimp_draw_tab_begin(wimp, width, height, &white_bg[0], &grey_bg[0]); + + for (i = 0; i <= wimp_SYSTEM_TAB_END; i++) + wimp_draw_tab(wimp, i, width, height, &pure_white[0]); + + wimp_draw_tab_end(wimp, width, height, header_height, &blue_bg[0]); + } + + wimp_render_quad(wimp, 0, header_height, width, + wimp->shadow_height, + width, height, + &shadow_bg[0]); + + title_margin = wimp->margin; + + if (menu_entries_ctl(MENU_ENTRIES_CTL_SHOW_BACK, NULL)) + { + title_margin = wimp->icon_size; + wimp_draw_icon(wimp, wimp->textures.list[wimp_TEXTURE_BACK].id, + 0, 0, width, height, 0, 1, &pure_white[0]); + } + + ticker_limit = (width - wimp->margin*2) / wimp->glyph_width; + menu_animation_ticker_str(title_buf, ticker_limit, + *frame_count / 100, title, true); + + /* Title */ + if (wimp_get_core_title(title_msg, sizeof(title_msg)) == 0) + { + char title_buf_msg_tmp[256]; + char title_buf_msg[256]; + size_t usable_width = width - (wimp->margin * 2); + int ticker_limit, value_len; + + snprintf(title_buf_msg, sizeof(title_buf), "%s (%s)", + title_buf, title_msg); + value_len = strlen(title_buf); + ticker_limit = (usable_width / wimp->glyph_width) - (value_len + 2); + + menu_animation_ticker_str(title_buf_msg_tmp, + ticker_limit, *frame_count / 20, title_buf_msg, true); + + strlcpy(title_buf, title_buf_msg_tmp, sizeof(title_buf)); + } + + wimp_blit_line(title_margin, header_height / 2, width, height, + title_buf, title_color, TEXT_ALIGN_LEFT); + + wimp_draw_scrollbar(wimp, width, height, &grey_bg[0]); + + menu_input_ctl(MENU_INPUT_CTL_KEYBOARD_DISPLAY, &display_kb); + + if (display_kb) + { + const char *str = NULL, *label = NULL; + menu_input_ctl(MENU_INPUT_CTL_KEYBOARD_BUFF_PTR, &str); + menu_input_ctl(MENU_INPUT_CTL_KEYBOARD_LABEL, &label); + + if (!str) + str = ""; + wimp_render_quad(wimp, 0, 0, width, height, width, height, &black_bg[0]); + snprintf(msg, sizeof(msg), "%s\n%s", label, str); + wimp_render_messagebox(msg); + } + + if (!string_is_empty(wimp->box_message)) + { + wimp_render_quad(wimp, 0, 0, width, height, width, height, &black_bg[0]); + wimp_render_messagebox(wimp->box_message); + wimp->box_message[0] = '\0'; + } + + if (settings->menu.mouse.enable && (settings->video.fullscreen + || !video_driver_ctl(RARCH_DISPLAY_CTL_HAS_WINDOWED, NULL))) + { + int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + + wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); + } + + menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); + menu_display_ctl(MENU_DISPLAY_CTL_UNSET_VIEWPORT, NULL); +} + +static void wimp_allocate_white_texture(wimp_handle_t *wimp) +{ + struct texture_image ti; + static const uint8_t white_data[] = { 0xff, 0xff, 0xff, 0xff }; + + ti.width = 1; + ti.height = 1; + ti.pixels = (uint32_t*)&white_data; + + video_driver_texture_load(&ti, + TEXTURE_FILTER_NEAREST, (unsigned*)&wimp->textures.white); +} + +static void wimp_font(void) +{ + int font_size; + char mediapath[PATH_MAX_LENGTH], fontpath[PATH_MAX_LENGTH]; + menu_display_ctx_font_t font_info; + settings_t *settings = config_get_ptr(); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_SIZE, &font_size); + + fill_pathname_join(mediapath, settings->assets_directory, + "wimp", sizeof(mediapath)); + fill_pathname_join(fontpath, mediapath, + "Roboto-Regular.ttf", sizeof(fontpath)); + + font_info.path = fontpath; + font_info.size = font_size; + + if (!menu_display_ctl(MENU_DISPLAY_CTL_FONT_MAIN_INIT, &font_info)) + RARCH_WARN("Failed to load font."); +} + +static void wimp_layout(wimp_handle_t *wimp) +{ + void *fb_buf; + float scale_factor; + int new_font_size; + unsigned width, height, new_header_height; + + video_driver_get_size(&width, &height); + + /* Mobiles platforms may have very small display metrics + * coupled to a high resolution, so we should be DPI aware + * to ensure the entries hitboxes are big enough. + * + * On desktops, we just care about readability, with every widget + * size proportional to the display width. */ + menu_display_ctl(MENU_DISPLAY_CTL_GET_DPI, &scale_factor); + + new_header_height = scale_factor / 3; + new_font_size = scale_factor / 9; + + wimp->shadow_height = scale_factor / 36; + wimp->scrollbar_width = scale_factor / 36; + wimp->tabs_height = scale_factor / 3; + wimp->line_height = scale_factor / 3; + wimp->margin = scale_factor / 9; + wimp->icon_size = scale_factor / 3; + + menu_display_ctl(MENU_DISPLAY_CTL_SET_HEADER_HEIGHT, + &new_header_height); + menu_display_ctl(MENU_DISPLAY_CTL_SET_FONT_SIZE, + &new_font_size); + + /* we assume the average glyph aspect ratio is close to 3:4 */ + wimp->glyph_width = new_font_size * 3/4; + + wimp_font(); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_BUF, &fb_buf); + + if (fb_buf) /* calculate a more realistic ticker_limit */ + { + unsigned m_width = + font_driver_get_message_width(fb_buf, "a", 1, 1); + + if (m_width) + wimp->glyph_width = m_width; + } +} + +static void *wimp_init(void **userdata) +{ + wimp_handle_t *wimp = NULL; + menu_handle_t *menu = (menu_handle_t*) + calloc(1, sizeof(*menu)); + + if (!menu) + goto error; + + if (!menu_display_ctl(MENU_DISPLAY_CTL_INIT_FIRST_DRIVER, NULL)) + goto error; + + wimp = (wimp_handle_t*)calloc(1, sizeof(wimp_handle_t)); + + if (!wimp) + goto error; + + *userdata = wimp; + + wimp_layout(wimp); + wimp_allocate_white_texture(wimp); + + init(); + + return menu; +error: + if (menu) + free(menu); + return NULL; +} + +static void wimp_free(void *data) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return; + + gfx_coord_array_free(&wimp->list_block.carr); + + font_driver_bind_block(NULL, NULL); +} + +static void wimp_context_bg_destroy(wimp_handle_t *wimp) +{ + if (!wimp) + return; + + video_driver_texture_unload((uintptr_t*)&wimp->textures.bg.id); + video_driver_texture_unload((uintptr_t*)&wimp->textures.white); +} + +static void wimp_context_destroy(void *data) +{ + unsigned i; + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return; + + for (i = 0; i < wimp_TEXTURE_LAST; i++) + video_driver_texture_unload((uintptr_t*)&wimp->textures.list[i].id); + + menu_display_ctl(MENU_DISPLAY_CTL_FONT_MAIN_DEINIT, NULL); + + wimp_context_bg_destroy(wimp); +} + +static bool wimp_load_image(void *userdata, void *data, + menu_image_type_t type) +{ + wimp_handle_t *wimp = (wimp_handle_t*)userdata; + + switch (type) + { + case MENU_IMAGE_NONE: + break; + case MENU_IMAGE_WALLPAPER: + wimp_context_bg_destroy(wimp); + video_driver_texture_load(data, + TEXTURE_FILTER_MIPMAP_LINEAR, (unsigned*)&wimp->textures.bg.id); + wimp_allocate_white_texture(wimp); + break; + case MENU_IMAGE_BOXART: + break; + } + + return true; +} + +static float wimp_get_scroll(wimp_handle_t *wimp) +{ + size_t selection; + unsigned width, height, half = 0; + + if (!wimp) + return 0; + if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) + return 0; + + video_driver_get_size(&width, &height); + + if (wimp->line_height) + half = (height / wimp->line_height) / 2; + + if (selection < half) + return 0; + + return ((selection + 2 - half) * wimp->line_height); +} + +static void wimp_navigation_set(void *data, bool scroll) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + float scroll_pos = wimp ? wimp_get_scroll(wimp) : 0.0f; + + if (!wimp || !scroll) + return; + + menu_animation_push(10, scroll_pos, + &wimp->scroll_y, EASING_IN_OUT_QUAD, -1, NULL); +} + +static void wimp_list_set_selection(void *data, file_list_t *list) +{ + wimp_navigation_set(data, true); +} + +static void wimp_navigation_clear(void *data, bool pending_push) +{ + size_t i = 0; + wimp_handle_t *wimp = (wimp_handle_t*)data; + if (!wimp) + return; + + menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i); + wimp->scroll_y = 0; +} + +static void wimp_navigation_set_last(void *data) +{ + wimp_navigation_set(data, true); +} + +static void wimp_navigation_alphabet(void *data, size_t *unused) +{ + wimp_navigation_set(data, true); +} + +static void wimp_populate_entries( + void *data, const char *path, + const char *label, unsigned i) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + if (!wimp) + return; + + wimp->scroll_y = wimp_get_scroll(wimp); +} + +static void wimp_context_reset(void *data) +{ + char iconpath[PATH_MAX_LENGTH] = {0}; + wimp_handle_t *wimp = (wimp_handle_t*)data; + settings_t *settings = config_get_ptr(); + + if (!wimp || !settings) + return; + + fill_pathname_join(iconpath, settings->assets_directory, + "glui", sizeof(iconpath)); + fill_pathname_slash(iconpath, sizeof(iconpath)); + + wimp_layout(wimp); + wimp_context_bg_destroy(wimp); + wimp_allocate_white_texture(wimp); + wimp_context_reset_textures(wimp, iconpath); + + rarch_task_push_image_load(settings->menu.wallpaper, "cb_menu_wallpaper", + menu_display_handle_wallpaper_upload, NULL); +} + +static int wimp_environ(menu_environ_cb_t type, void *data, void *userdata) +{ + switch (type) + { + case 0: + default: + break; + } + + return -1; +} + +static void wimp_preswitch_tabs(wimp_handle_t *wimp, unsigned action) +{ + size_t idx = 0; + size_t stack_size = 0; + file_list_t *menu_stack = NULL; + + if (!wimp) + return; + + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET_SELECTION, &idx); + + menu_stack = menu_entries_get_menu_stack_ptr(0); + stack_size = menu_stack->size; + + if (menu_stack->list[stack_size - 1].label) + free(menu_stack->list[stack_size - 1].label); + menu_stack->list[stack_size - 1].label = NULL; + + switch (wimp->categories.selection_ptr) + { + case wimp_SYSTEM_TAB_MAIN: + menu_stack->list[stack_size - 1].label = + strdup(menu_hash_to_str(MENU_VALUE_MAIN_MENU)); + menu_stack->list[stack_size - 1].type = + MENU_SETTINGS; + break; + case wimp_SYSTEM_TAB_PLAYLISTS: + menu_stack->list[stack_size - 1].label = + strdup(menu_hash_to_str(MENU_VALUE_PLAYLISTS_TAB)); + menu_stack->list[stack_size - 1].type = + MENU_PLAYLISTS_TAB; + break; + case wimp_SYSTEM_TAB_SETTINGS: + menu_stack->list[stack_size - 1].label = + strdup(menu_hash_to_str(MENU_VALUE_SETTINGS_TAB)); + menu_stack->list[stack_size - 1].type = + MENU_SETTINGS; + break; + } +} + +static void wimp_list_cache(void *data, menu_list_type_t type, unsigned action) +{ + size_t list_size; + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return; + + list_size = wimp_SYSTEM_TAB_END; + + switch (type) + { + case MENU_LIST_PLAIN: + break; + case MENU_LIST_HORIZONTAL: + wimp->categories.selection_ptr_old = wimp->categories.selection_ptr; + + switch (action) + { + case MENU_ACTION_LEFT: + if (wimp->categories.selection_ptr == 0) + { + wimp->categories.selection_ptr = list_size; + wimp->categories.active.idx = list_size - 1; + } + else + wimp->categories.selection_ptr--; + break; + default: + if (wimp->categories.selection_ptr == list_size) + { + wimp->categories.selection_ptr = 0; + wimp->categories.active.idx = 1; + } + else + wimp->categories.selection_ptr++; + break; + } + + wimp_preswitch_tabs(wimp, action); + break; + default: + break; + } +} + +static int wimp_list_push(void *data, void *userdata, + menu_displaylist_info_t *info, unsigned type) +{ + int ret = -1; + core_info_list_t *list = NULL; + menu_handle_t *menu = (menu_handle_t*)data; + + (void)userdata; + + switch (type) + { + case DISPLAYLIST_LOAD_CONTENT_LIST: + menu_entries_clear(info->list); + menu_entries_push(info->list, + menu_hash_to_str(MENU_LABEL_VALUE_LOAD_CONTENT), + menu_hash_to_str(MENU_LABEL_LOAD_CONTENT), + MENU_SETTING_ACTION, 0, 0); + + core_info_ctl(CORE_INFO_CTL_LIST_GET, &list); + if (core_info_list_num_info_files(list)) + { + menu_entries_push(info->list, + menu_hash_to_str(MENU_LABEL_VALUE_DETECT_CORE_LIST), + menu_hash_to_str(MENU_LABEL_DETECT_CORE_LIST), + MENU_SETTING_ACTION, 0, 0); + + menu_entries_push(info->list, + menu_hash_to_str(MENU_LABEL_VALUE_DOWNLOADED_FILE_DETECT_CORE_LIST), + menu_hash_to_str(MENU_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST), + MENU_SETTING_ACTION, 0, 0); + } + + info->need_push = true; + info->need_refresh = true; + ret = 0; + break; + case DISPLAYLIST_MAIN_MENU: + menu_entries_clear(info->list); + + if (!rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) + { + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_CONTENT_SETTINGS), PARSE_ACTION, false); + } + + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_START_CORE), PARSE_ACTION, false); + +#ifndef HAVE_DYNAMIC + if (frontend_driver_has_fork()) +#endif + { + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_CORE_LIST), PARSE_ACTION, false); + } + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_LOAD_CONTENT_LIST), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_LOAD_CONTENT_HISTORY), PARSE_ACTION, false); +#if defined(HAVE_NETWORKING) +#if defined(HAVE_LIBRETRODB) + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_ADD_CONTENT_LIST), PARSE_ACTION, false); +#endif + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_ONLINE_UPDATER), PARSE_ACTION, false); +#endif + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_INFORMATION_LIST), PARSE_ACTION, false); +#ifndef HAVE_DYNAMIC + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_RESTART_RETROARCH), PARSE_ACTION, false); +#endif + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_CONFIGURATIONS), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_SAVE_CURRENT_CONFIG), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_SAVE_NEW_CONFIG), PARSE_ACTION, false); + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_HELP_LIST), PARSE_ACTION, false); +#if !defined(IOS) + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_QUIT_RETROARCH), PARSE_ACTION, false); +#endif +#if defined(HAVE_LAKKA) + menu_displaylist_parse_settings(menu, info, + menu_hash_to_str(MENU_LABEL_SHUTDOWN), PARSE_ACTION, false); +#endif + info->need_push = true; + ret = 0; + break; + } + return ret; +} + +static size_t wimp_list_get_selection(void *data) +{ + wimp_handle_t *wimp = (wimp_handle_t*)data; + + if (!wimp) + return 0; + + return wimp->categories.selection_ptr; +} + +static int wimp_pointer_tap(void *userdata, + unsigned x, unsigned y, + unsigned ptr, menu_file_list_cbs_t *cbs, + menu_entry_t *entry, unsigned action) +{ + size_t selection, idx; + unsigned header_height, width, height, i; + bool scroll = false; + wimp_handle_t *wimp = (wimp_handle_t*)userdata; + file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); + file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); + + if (!wimp) + return 0; + + video_driver_get_size(&width, &height); + + menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection); + menu_display_ctl(MENU_DISPLAY_CTL_HEADER_HEIGHT, &header_height); + + if (y < header_height) + { + menu_entries_pop_stack(&selection, 0); + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET_SELECTION, &selection); + } + else if (y > height - wimp->tabs_height) + { + for (i = 0; i <= wimp_SYSTEM_TAB_END; i++) + { + unsigned tab_width = width / (wimp_SYSTEM_TAB_END + 1); + unsigned start = tab_width * i; + + if ((x >= start) && (x < (start + tab_width))) + { + wimp->categories.selection_ptr = i; + + wimp_preswitch_tabs(wimp, action); + + if (cbs && cbs->action_content_list_switch) + return cbs->action_content_list_switch(selection_buf, menu_stack, + "", "", 0); + } + } + } + else if (ptr <= (menu_entries_get_size() - 1)) + { + if (ptr == selection && cbs && cbs->action_select) + return menu_entry_action(entry, selection, MENU_ACTION_SELECT); + + idx = ptr; + + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET_SELECTION, &idx); + menu_navigation_ctl(MENU_NAVIGATION_CTL_SET, &scroll); + } + + return 0; +} + +menu_ctx_driver_t menu_ctx_wimp = { + NULL, + wimp_get_message, + generic_menu_iterate, + wimp_render, + wimp_frame, + wimp_init, + wimp_free, + wimp_context_reset, + wimp_context_destroy, + wimp_populate_entries, + NULL, + wimp_navigation_clear, + NULL, + NULL, + wimp_navigation_set, + wimp_navigation_set_last, + wimp_navigation_alphabet, + wimp_navigation_alphabet, + generic_menu_init_list, + NULL, + NULL, + NULL, + wimp_list_cache, + wimp_list_push, + wimp_list_get_selection, + wimp_list_get_size, + NULL, + wimp_list_set_selection, + NULL, + wimp_load_image, + "wimp", + wimp_environ, + wimp_pointer_tap, +}; From dbfc9ff370473ee9abe8e143cd16c29ded66086e Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 15:30:48 -0500 Subject: [PATCH 04/26] remove init and zdraw functions --- menu/drivers/wimp.c | 49 +++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index a2d2734b8e..6377904112 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -474,34 +474,6 @@ int width = 0, height = 0; struct zr_user_font usrfnt; struct zr_allocator alloc; -void init() -{ - settings_t *settings = config_get_ptr(); - fill_pathname_join(font_path, settings->assets_directory, - "wimp", sizeof(font_path)); - printf("Font: \n%s\n\n\n",settings->assets_directory); - fill_pathname_join(font_path, font_path, - "Roboto-Regular.ttf", sizeof(font_path)); - printf("Font: %s\n\n\n\n",font_path); -} - -void zdraw(int width, int height) -{ - /* OpenGL */ - glViewport(0, 0, width, height); - alloc.userdata.ptr = NULL; - alloc.alloc = mem_alloc; - alloc.free = mem_free; - zr_buffer_init(&device.cmds, &alloc, 1024); - usrfnt = font_bake_and_upload(&device, &font, font_path, 14, - zr_font_default_glyph_ranges()); - zr_init(&gui.ctx, &alloc, &usrfnt); - device_init(&device); - run_demo(&gui); - glViewport(0, 0, width, height); - device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); -} - enum { wimp_TEXTURE_POINTER = 0, @@ -1332,8 +1304,6 @@ static void wimp_frame(void *data) } } - zdraw(width, height); - menu_entries_get_title(title, sizeof(title)); if (!menu_navigation_ctl(MENU_NAVIGATION_CTL_GET_SELECTION, &selection)) @@ -1449,6 +1419,19 @@ static void wimp_frame(void *data) wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); } + glViewport(0, 0, width, height); + alloc.userdata.ptr = NULL; + alloc.alloc = mem_alloc; + alloc.free = mem_free; + zr_buffer_init(&device.cmds, &alloc, 1024); + usrfnt = font_bake_and_upload(&device, &font, font_path, 14, + zr_font_default_glyph_ranges()); + zr_init(&gui.ctx, &alloc, &usrfnt); + device_init(&device); + run_demo(&gui); + glViewport(0, 0, width, height); + device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); + menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); menu_display_ctl(MENU_DISPLAY_CTL_UNSET_VIEWPORT, NULL); } @@ -1558,7 +1541,11 @@ static void *wimp_init(void **userdata) wimp_layout(wimp); wimp_allocate_white_texture(wimp); - init(); + settings_t *settings = config_get_ptr(); + fill_pathname_join(font_path, settings->assets_directory, + "wimp", sizeof(font_path)); + fill_pathname_join(font_path, font_path, + "Roboto-Regular.ttf", sizeof(font_path)); return menu; error: From 031fc859cea03cc159f44ca367ec0099d2eb87b7 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 15:33:24 -0500 Subject: [PATCH 05/26] remove init and zdraw functions --- menu/drivers/wimp.c | 47 ++++++++++++--------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 6377904112..bac903d4d0 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -75,16 +75,15 @@ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; -struct demo { +struct wimp { void *memory; struct zr_context ctx; enum theme theme; struct zr_memory_status status; }; -static void -simple_window(struct zr_context *ctx) + +static void wimp_demo(struct zr_context *ctx) { - /* simple demo window */ struct zr_panel layout; if (zr_begin(ctx, &layout, "Show", zr_rect(100, 100, 200, 200), ZR_WINDOW_BORDER|ZR_WINDOW_MOVABLE|ZR_WINDOW_SCALABLE| @@ -108,8 +107,7 @@ simple_window(struct zr_context *ctx) zr_end(ctx); } -static int -run_demo(struct demo *gui) +static int run_demo(struct wimp *gui) { int ret = 1; static int init = 0; @@ -120,32 +118,16 @@ run_demo(struct demo *gui) } /* windows */ - simple_window(ctx); + wimp_demo(ctx); // ret = control_window(ctx, gui); zr_buffer_info(&gui->status, &gui->ctx.memory); return ret; } -static struct demo gui; -/* ============================================================== - * - * Utility - * - * ===============================================================*/ -static void -die(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fputs("\n", stderr); - exit(EXIT_FAILURE); -} +static struct wimp gui; -static char* -file_load(const char* path, size_t* siz) +static char* file_load(const char* path, size_t* siz) { char *buf; FILE *fd = fopen(path, "rb"); @@ -177,8 +159,7 @@ struct device { GLuint font_tex; }; -static void -device_init(struct device *dev) +static void device_init(struct device *dev) { GLint status; static const GLchar *vertex_shader = @@ -258,8 +239,7 @@ device_init(struct device *dev) glBindVertexArray(0); } -static struct zr_user_font -font_bake_and_upload(struct device *dev, struct zr_font *font, +static struct zr_user_font font_bake_and_upload(struct device *dev, struct zr_font *font, const char *path, unsigned int font_height, const zr_rune *range) { int glyph_count; @@ -346,8 +326,7 @@ font_bake_and_upload(struct device *dev, struct zr_font *font, return user_font; } -static void -device_shutdown(struct device *dev) +static void device_shutdown(struct device *dev) { glDetachShader(dev->prog, dev->vert_shdr); glDetachShader(dev->prog, dev->frag_shdr); @@ -359,8 +338,7 @@ device_shutdown(struct device *dev) glDeleteBuffers(1, &dev->ebo); } -static void -device_draw(struct device *dev, struct zr_context *ctx, int width, int height, +static void device_draw(struct device *dev, struct zr_context *ctx, int width, int height, enum zr_anti_aliasing AA) { GLint last_prog, last_tex; @@ -455,8 +433,7 @@ device_draw(struct device *dev, struct zr_context *ctx, int width, int height, glDisable(GL_SCISSOR_TEST); } -static void -error_callback(int error, const char *description) +static void error_callback(int error, const char *description) { fprintf(stderr, "Error %d: %s\n", error, description); } From 337679976880b188ff86d5b5a570d4fb705da860 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 15:35:19 -0500 Subject: [PATCH 06/26] remove more unused code --- menu/drivers/wimp.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index bac903d4d0..40019b8dd5 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -131,7 +131,7 @@ static char* file_load(const char* path, size_t* siz) { char *buf; FILE *fd = fopen(path, "rb"); - if (!fd) die("Failed to open file: %s\n", path); + fseek(fd, 0, SEEK_END); *siz = (size_t)ftell(fd); fseek(fd, 0, SEEK_SET); @@ -261,9 +261,6 @@ static struct zr_user_font font_bake_and_upload(struct device *dev, struct zr_fo const char *custom_data = "...."; struct zr_font_config config; char *ttf_blob = file_load(path, &ttf_size); - if (!ttf_blob) - die("[Font]: %s is not a file or cannot be found!\n", path); - /* setup font configuration */ memset(&config, 0, sizeof(config)); config.ttf_blob = ttf_blob; @@ -284,8 +281,7 @@ static struct zr_user_font font_bake_and_upload(struct device *dev, struct zr_fo /* pack all glyphes and return needed image width, height and memory size*/ custom.w = 2; custom.h = 2; - if (!zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1)) - die("[Font]: failed to load font!\n"); + zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1); /* bake all glyphes and custom white pixel into image */ img = calloc(1, img_size); From 172cdc1f491ce753d8dbda90ced488cfcb952ff1 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 15:39:54 -0500 Subject: [PATCH 07/26] remove more unused code and deinit properly --- menu/drivers/wimp.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 40019b8dd5..359abf54d2 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -429,11 +429,6 @@ static void device_draw(struct device *dev, struct zr_context *ctx, int width, i glDisable(GL_SCISSOR_TEST); } -static void error_callback(int error, const char *description) -{ - fprintf(stderr, "Error %d: %s\n", error, description); -} - static void* mem_alloc(zr_handle unused, size_t size) {UNUSED(unused); return calloc(1, size);} static void mem_free(zr_handle unused, void *ptr) @@ -1533,6 +1528,12 @@ static void wimp_free(void *data) if (!wimp) return; + + + free(font.glyphs); + zr_free(&gui.ctx); + zr_buffer_free(&device.cmds); + device_shutdown(&device); gfx_coord_array_free(&wimp->list_block.carr); From 0cbdd4f3a654d306b12e16bbcff25abf213cfcd3 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 15:45:19 -0500 Subject: [PATCH 08/26] fix indentation a bit --- menu/drivers/wimp.c | 173 +++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 82 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 359abf54d2..b88298e0ce 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -335,104 +335,113 @@ static void device_shutdown(struct device *dev) } static void device_draw(struct device *dev, struct zr_context *ctx, int width, int height, - enum zr_anti_aliasing AA) + enum zr_anti_aliasing AA) { - GLint last_prog, last_tex; - GLint last_ebo, last_vbo, last_vao; - GLfloat ortho[4][4] = { - {2.0f, 0.0f, 0.0f, 0.0f}, - {0.0f,-2.0f, 0.0f, 0.0f}, - {0.0f, 0.0f,-1.0f, 0.0f}, - {-1.0f,1.0f, 0.0f, 1.0f}, - }; - ortho[0][0] /= (GLfloat)width; - ortho[1][1] /= (GLfloat)height; + GLint last_prog, last_tex; + GLint last_ebo, last_vbo, last_vao; + GLfloat ortho[4][4] = { + {2.0f, 0.0f, 0.0f, 0.0f}, + {0.0f,-2.0f, 0.0f, 0.0f}, + {0.0f, 0.0f,-1.0f, 0.0f}, + {-1.0f,1.0f, 0.0f, 1.0f}, + }; + ortho[0][0] /= (GLfloat)width; + ortho[1][1] /= (GLfloat)height; - /* save previous opengl state */ - glGetIntegerv(GL_CURRENT_PROGRAM, &last_prog); - glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); - glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_vao); - glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_ebo); - glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vbo); + /* save previous opengl state */ + glGetIntegerv(GL_CURRENT_PROGRAM, &last_prog); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_vao); + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_ebo); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vbo); - /* setup global state */ - glEnable(GL_BLEND); - glBlendEquation(GL_FUNC_ADD); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glEnable(GL_SCISSOR_TEST); - glActiveTexture(GL_TEXTURE0); + /* setup global state */ + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glActiveTexture(GL_TEXTURE0); - /* setup program */ - glUseProgram(dev->prog); - glUniform1i(dev->uniform_tex, 0); - glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]); + /* setup program */ + glUseProgram(dev->prog); + glUniform1i(dev->uniform_tex, 0); + glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]); - { - /* convert from command queue into draw list and draw to screen */ - const struct zr_draw_command *cmd; - void *vertices, *elements; - const zr_draw_index *offset = NULL; + { + /* convert from command queue into draw list and draw to screen */ + const struct zr_draw_command *cmd; + void *vertices, *elements; + const zr_draw_index *offset = NULL; - /* allocate vertex and element buffer */ - glBindVertexArray(dev->vao); - glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); + /* allocate vertex and element buffer */ + glBindVertexArray(dev->vao); + glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); - glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW); + glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW); - /* load draw vertices & elements directly into vertex + element buffer */ - vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); - elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); - { - struct zr_buffer vbuf, ebuf; + /* load draw vertices & elements directly into vertex + element buffer */ + vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); + elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY); + { + struct zr_buffer vbuf, ebuf; - /* fill converting configuration */ - struct zr_convert_config config; - memset(&config, 0, sizeof(config)); - config.global_alpha = 1.0f; - config.shape_AA = AA; - config.line_AA = AA; - config.circle_segment_count = 22; - config.line_thickness = 1.0f; - config.null = dev->null; + /* fill converting configuration */ + struct zr_convert_config config; + memset(&config, 0, sizeof(config)); + config.global_alpha = 1.0f; + config.shape_AA = AA; + config.line_AA = AA; + config.circle_segment_count = 22; + config.line_thickness = 1.0f; + config.null = dev->null; - /* setup buffers to load vertices and elements */ - zr_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY); - zr_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY); - zr_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config); - } - glUnmapBuffer(GL_ARRAY_BUFFER); - glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); + /* setup buffers to load vertices and elements */ + zr_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY); + zr_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY); + zr_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config); + } + glUnmapBuffer(GL_ARRAY_BUFFER); + glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); - /* iterate over and execute each draw command */ - zr_draw_foreach(cmd, ctx, &dev->cmds) { - if (!cmd->elem_count) continue; - glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); - glScissor((GLint)cmd->clip_rect.x, - height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h), - (GLint)cmd->clip_rect.w, (GLint)cmd->clip_rect.h); - glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); - offset += cmd->elem_count; - } - zr_clear(ctx); - } + /* iterate over and execute each draw command */ + zr_draw_foreach(cmd, ctx, &dev->cmds) + { + if (!cmd->elem_count) + continue; + glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); + glScissor((GLint)cmd->clip_rect.x, + height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h), + (GLint)cmd->clip_rect.w, (GLint)cmd->clip_rect.h); + glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); + offset += cmd->elem_count; + } + zr_clear(ctx); + } - /* restore old state */ - glUseProgram((GLuint)last_prog); - glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex); - glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)last_ebo); - glBindVertexArray((GLuint)last_vao); - glDisable(GL_SCISSOR_TEST); + /* restore old state */ + glUseProgram((GLuint)last_prog); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex); + glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)last_ebo); + glBindVertexArray((GLuint)last_vao); + glDisable(GL_SCISSOR_TEST); } static void* mem_alloc(zr_handle unused, size_t size) -{UNUSED(unused); return calloc(1, size);} +{ + UNUSED(unused); + return calloc(1, size); +} + static void mem_free(zr_handle unused, void *ptr) -{UNUSED(unused); free(ptr);} +{ + UNUSED(unused); + free(ptr); +} struct device device; struct zr_font font; From e5feaacf2ce19278465fb4e32ff047a22858a59a Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 15:56:54 -0500 Subject: [PATCH 09/26] fix indentation a bit --- menu/drivers/wimp.c | 387 ++++++++++++++++++++++---------------------- 1 file changed, 193 insertions(+), 194 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index b88298e0ce..b580132e76 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -76,10 +76,10 @@ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; struct wimp { - void *memory; - struct zr_context ctx; - enum theme theme; - struct zr_memory_status status; + void *memory; + struct zr_context ctx; + enum theme theme; + struct zr_memory_status status; }; static void wimp_demo(struct zr_context *ctx) @@ -89,249 +89,248 @@ static void wimp_demo(struct zr_context *ctx) ZR_WINDOW_BORDER|ZR_WINDOW_MOVABLE|ZR_WINDOW_SCALABLE| ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_TITLE)) { - enum {EASY, HARD}; - static int op = EASY; - static int property = 20; + enum {EASY, HARD}; + static int op = EASY; + static int property = 20; - zr_layout_row_static(ctx, 30, 80, 1); - if (zr_button_text(ctx, "button", ZR_BUTTON_DEFAULT)) { + zr_layout_row_static(ctx, 30, 80, 1); + if (zr_button_text(ctx, "button", ZR_BUTTON_DEFAULT)) { /* event handling */ - } - zr_layout_row_dynamic(ctx, 30, 2); - if (zr_option(ctx, "easy", op == EASY)) op = EASY; - if (zr_option(ctx, "hard", op == HARD)) op = HARD; + } + zr_layout_row_dynamic(ctx, 30, 2); + if (zr_option(ctx, "easy", op == EASY)) + op = EASY; + if (zr_option(ctx, "hard", op == HARD)) + op = HARD; - zr_layout_row_dynamic(ctx, 22, 1); - zr_property_int(ctx, "Compression:", 0, &property, 100, 10, 1); - } - zr_end(ctx); + zr_layout_row_dynamic(ctx, 22, 1); + zr_property_int(ctx, "Compression:", 0, &property, 100, 10, 1); + } + zr_end(ctx); } static int run_demo(struct wimp *gui) { - int ret = 1; - static int init = 0; - struct zr_context *ctx = &gui->ctx; + int ret = 1; + static int init = 0; + struct zr_context *ctx = &gui->ctx; - if (!init) { - init = 1; - } + if (!init) { + init = 1; + } - /* windows */ - wimp_demo(ctx); - -// ret = control_window(ctx, gui); - zr_buffer_info(&gui->status, &gui->ctx.memory); - return ret; + wimp_demo(ctx); + zr_buffer_info(&gui->status, &gui->ctx.memory); + return ret; } static struct wimp gui; static char* file_load(const char* path, size_t* siz) { - char *buf; - FILE *fd = fopen(path, "rb"); + char *buf; + FILE *fd = fopen(path, "rb"); - fseek(fd, 0, SEEK_END); - *siz = (size_t)ftell(fd); - fseek(fd, 0, SEEK_SET); - buf = (char*)calloc(*siz, 1); - fread(buf, *siz, 1, fd); - fclose(fd); - return buf; + fseek(fd, 0, SEEK_END); + *siz = (size_t)ftell(fd); + fseek(fd, 0, SEEK_SET); + buf = (char*)calloc(*siz, 1); + fread(buf, *siz, 1, fd); + fclose(fd); + return buf; } struct device { - struct zr_buffer cmds; - struct zr_draw_null_texture null; - GLuint vbo, vao, ebo; + struct zr_buffer cmds; + struct zr_draw_null_texture null; + GLuint vbo, vao, ebo; - GLuint prog; - GLuint vert_shdr; - GLuint frag_shdr; + GLuint prog; + GLuint vert_shdr; + GLuint frag_shdr; - GLint attrib_pos; - GLint attrib_uv; - GLint attrib_col; + GLint attrib_pos; + GLint attrib_uv; + GLint attrib_col; - GLint uniform_tex; - GLint uniform_proj; - GLuint font_tex; + GLint uniform_tex; + GLint uniform_proj; + GLuint font_tex; }; static void device_init(struct device *dev) { - GLint status; - static const GLchar *vertex_shader = - "#version 300 es\n" - "uniform mat4 ProjMtx;\n" - "in vec2 Position;\n" - "in vec2 TexCoord;\n" - "in vec4 Color;\n" - "out vec2 Frag_UV;\n" - "out vec4 Frag_Color;\n" - "void main() {\n" - " Frag_UV = TexCoord;\n" - " Frag_Color = Color;\n" - " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" - "}\n"; - static const GLchar *fragment_shader = - "#version 300 es\n" - "precision mediump float;\n" - "uniform sampler2D Texture;\n" - "in vec2 Frag_UV;\n" - "in vec4 Frag_Color;\n" - "out vec4 Out_Color;\n" - "void main(){\n" - " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" - "}\n"; + GLint status; + static const GLchar *vertex_shader = + "#version 300 es\n" + "uniform mat4 ProjMtx;\n" + "in vec2 Position;\n" + "in vec2 TexCoord;\n" + "in vec4 Color;\n" + "out vec2 Frag_UV;\n" + "out vec4 Frag_Color;\n" + "void main() {\n" + " Frag_UV = TexCoord;\n" + " Frag_Color = Color;\n" + " gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" + "}\n"; + static const GLchar *fragment_shader = + "#version 300 es\n" + "precision mediump float;\n" + "uniform sampler2D Texture;\n" + "in vec2 Frag_UV;\n" + "in vec4 Frag_Color;\n" + "out vec4 Out_Color;\n" + "void main(){\n" + " Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" + "}\n"; - dev->prog = glCreateProgram(); - dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER); - dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0); - glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0); - glCompileShader(dev->vert_shdr); - glCompileShader(dev->frag_shdr); - glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status); - assert(status == GL_TRUE); - glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status); - assert(status == GL_TRUE); - glAttachShader(dev->prog, dev->vert_shdr); - glAttachShader(dev->prog, dev->frag_shdr); - glLinkProgram(dev->prog); - glGetProgramiv(dev->prog, GL_LINK_STATUS, &status); - assert(status == GL_TRUE); + dev->prog = glCreateProgram(); + dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER); + dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0); + glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0); + glCompileShader(dev->vert_shdr); + glCompileShader(dev->frag_shdr); + glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status); + assert(status == GL_TRUE); + glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status); + assert(status == GL_TRUE); + glAttachShader(dev->prog, dev->vert_shdr); + glAttachShader(dev->prog, dev->frag_shdr); + glLinkProgram(dev->prog); + glGetProgramiv(dev->prog, GL_LINK_STATUS, &status); + assert(status == GL_TRUE); - dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture"); - dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx"); - dev->attrib_pos = glGetAttribLocation(dev->prog, "Position"); - dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord"); - dev->attrib_col = glGetAttribLocation(dev->prog, "Color"); + dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture"); + dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx"); + dev->attrib_pos = glGetAttribLocation(dev->prog, "Position"); + dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord"); + dev->attrib_col = glGetAttribLocation(dev->prog, "Color"); - { - /* buffer setup */ - GLsizei vs = sizeof(struct zr_draw_vertex); - size_t vp = offsetof(struct zr_draw_vertex, position); - size_t vt = offsetof(struct zr_draw_vertex, uv); - size_t vc = offsetof(struct zr_draw_vertex, col); + { + /* buffer setup */ + GLsizei vs = sizeof(struct zr_draw_vertex); + size_t vp = offsetof(struct zr_draw_vertex, position); + size_t vt = offsetof(struct zr_draw_vertex, uv); + size_t vc = offsetof(struct zr_draw_vertex, col); - glGenBuffers(1, &dev->vbo); - glGenBuffers(1, &dev->ebo); - glGenVertexArrays(1, &dev->vao); + glGenBuffers(1, &dev->vbo); + glGenBuffers(1, &dev->ebo); + glGenVertexArrays(1, &dev->vao); - glBindVertexArray(dev->vao); - glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); + glBindVertexArray(dev->vao); + glBindBuffer(GL_ARRAY_BUFFER, dev->vbo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo); - glEnableVertexAttribArray((GLuint)dev->attrib_pos); - glEnableVertexAttribArray((GLuint)dev->attrib_uv); - glEnableVertexAttribArray((GLuint)dev->attrib_col); + glEnableVertexAttribArray((GLuint)dev->attrib_pos); + glEnableVertexAttribArray((GLuint)dev->attrib_uv); + glEnableVertexAttribArray((GLuint)dev->attrib_col); - glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp); - glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt); - glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc); - } + glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp); + glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt); + glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc); + } - glBindTexture(GL_TEXTURE_2D, 0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindVertexArray(0); } static struct zr_user_font font_bake_and_upload(struct device *dev, struct zr_font *font, const char *path, unsigned int font_height, const zr_rune *range) { - int glyph_count; - int img_width, img_height; - struct zr_font_glyph *glyphes; - struct zr_baked_font baked_font; - struct zr_user_font user_font; - struct zr_recti custom; + int glyph_count; + int img_width, img_height; + struct zr_font_glyph *glyphes; + struct zr_baked_font baked_font; + struct zr_user_font user_font; + struct zr_recti custom; - memset(&baked_font, 0, sizeof(baked_font)); - memset(&user_font, 0, sizeof(user_font)); - memset(&custom, 0, sizeof(custom)); + memset(&baked_font, 0, sizeof(baked_font)); + memset(&user_font, 0, sizeof(user_font)); + memset(&custom, 0, sizeof(custom)); - { - /* bake and upload font texture */ - void *img, *tmp; - size_t ttf_size; - size_t tmp_size, img_size; - const char *custom_data = "...."; - struct zr_font_config config; - char *ttf_blob = file_load(path, &ttf_size); - /* setup font configuration */ - memset(&config, 0, sizeof(config)); - config.ttf_blob = ttf_blob; - config.ttf_size = ttf_size; - config.font = &baked_font; - config.coord_type = ZR_COORD_UV; - config.range = range; - config.pixel_snap = zr_false; - config.size = (float)font_height; - config.spacing = zr_vec2(0,0); - config.oversample_h = 1; - config.oversample_v = 1; + { + /* bake and upload font texture */ + void *img, *tmp; + size_t ttf_size; + size_t tmp_size, img_size; + const char *custom_data = "...."; + struct zr_font_config config; + char *ttf_blob = file_load(path, &ttf_size); + /* setup font configuration */ + memset(&config, 0, sizeof(config)); + config.ttf_blob = ttf_blob; + config.ttf_size = ttf_size; + config.font = &baked_font; + config.coord_type = ZR_COORD_UV; + config.range = range; + config.pixel_snap = zr_false; + config.size = (float)font_height; + config.spacing = zr_vec2(0,0); + config.oversample_h = 1; + config.oversample_v = 1; - /* query needed amount of memory for the font baking process */ - zr_font_bake_memory(&tmp_size, &glyph_count, &config, 1); - glyphes = (struct zr_font_glyph*)calloc(sizeof(struct zr_font_glyph), (size_t)glyph_count); - tmp = calloc(1, tmp_size); + /* query needed amount of memory for the font baking process */ + zr_font_bake_memory(&tmp_size, &glyph_count, &config, 1); + glyphes = (struct zr_font_glyph*)calloc(sizeof(struct zr_font_glyph), (size_t)glyph_count); + tmp = calloc(1, tmp_size); - /* pack all glyphes and return needed image width, height and memory size*/ - custom.w = 2; custom.h = 2; - zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1); + /* pack all glyphes and return needed image width, height and memory size*/ + custom.w = 2; custom.h = 2; + zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1); - /* bake all glyphes and custom white pixel into image */ - img = calloc(1, img_size); - zr_font_bake(img, img_width, img_height, tmp, tmp_size, glyphes, glyph_count, &config, 1); - zr_font_bake_custom_data(img, img_width, img_height, custom, custom_data, 2, 2, '.', 'X'); - { - /* convert alpha8 image into rgba8 image */ - void *img_rgba = calloc(4, (size_t)(img_height * img_width)); - zr_font_bake_convert(img_rgba, img_width, img_height, img); - free(img); - img = img_rgba; - } - { - /* upload baked font image */ - glGenTextures(1, &dev->font_tex); - glBindTexture(GL_TEXTURE_2D, dev->font_tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)img_width, (GLsizei)img_height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, img); - } - free(ttf_blob); - free(tmp); - free(img); - } + /* bake all glyphes and custom white pixel into image */ + img = calloc(1, img_size); + zr_font_bake(img, img_width, img_height, tmp, tmp_size, glyphes, glyph_count, &config, 1); + zr_font_bake_custom_data(img, img_width, img_height, custom, custom_data, 2, 2, '.', 'X'); + { + /* convert alpha8 image into rgba8 image */ + void *img_rgba = calloc(4, (size_t)(img_height * img_width)); + zr_font_bake_convert(img_rgba, img_width, img_height, img); + free(img); + img = img_rgba; + } + { + /* upload baked font image */ + glGenTextures(1, &dev->font_tex); + glBindTexture(GL_TEXTURE_2D, dev->font_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)img_width, (GLsizei)img_height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, img); + } + free(ttf_blob); + free(tmp); + free(img); + } - /* default white pixel in a texture which is needed to draw primitives */ - dev->null.texture.id = (int)dev->font_tex; - dev->null.uv = zr_vec2((custom.x + 0.5f)/(float)img_width, - (custom.y + 0.5f)/(float)img_height); + /* default white pixel in a texture which is needed to draw primitives */ + dev->null.texture.id = (int)dev->font_tex; + dev->null.uv = zr_vec2((custom.x + 0.5f)/(float)img_width, + (custom.y + 0.5f)/(float)img_height); - /* setup font with glyphes. IMPORTANT: the font only references the glyphes + /* setup font with glyphes. IMPORTANT: the font only references the glyphes this was done to have the possibility to have multible fonts with one total glyph array. Not quite sure if it is a good thing since the glyphes have to be freed as well. */ - zr_font_init(font, (float)font_height, '?', glyphes, &baked_font, dev->null.texture); - user_font = zr_font_ref(font); - return user_font; + zr_font_init(font, (float)font_height, '?', glyphes, &baked_font, dev->null.texture); + user_font = zr_font_ref(font); + return user_font; } static void device_shutdown(struct device *dev) { - glDetachShader(dev->prog, dev->vert_shdr); - glDetachShader(dev->prog, dev->frag_shdr); - glDeleteShader(dev->vert_shdr); - glDeleteShader(dev->frag_shdr); - glDeleteProgram(dev->prog); - glDeleteTextures(1, &dev->font_tex); - glDeleteBuffers(1, &dev->vbo); - glDeleteBuffers(1, &dev->ebo); + glDetachShader(dev->prog, dev->vert_shdr); + glDetachShader(dev->prog, dev->frag_shdr); + glDeleteShader(dev->vert_shdr); + glDeleteShader(dev->frag_shdr); + glDeleteProgram(dev->prog); + glDeleteTextures(1, &dev->font_tex); + glDeleteBuffers(1, &dev->vbo); + glDeleteBuffers(1, &dev->ebo); } static void device_draw(struct device *dev, struct zr_context *ctx, int width, int height, From 4fbb08210ca7de899ea185e67c5adfafd827f8a1 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 16:17:34 -0500 Subject: [PATCH 10/26] add some comments --- menu/drivers/wimp.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index b580132e76..5f75f97093 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -72,7 +72,7 @@ #define WINDOW_WIDTH 1200 #define WINDOW_HEIGHT 800 - +/* zahnrad code */ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; struct wimp { @@ -449,6 +449,7 @@ int width = 0, height = 0; struct zr_user_font usrfnt; struct zr_allocator alloc; +/* zahnrad code */ enum { @@ -1395,6 +1396,7 @@ static void wimp_frame(void *data) wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); } + /* zahnrad code */ glViewport(0, 0, width, height); alloc.userdata.ptr = NULL; alloc.alloc = mem_alloc; @@ -1407,6 +1409,7 @@ static void wimp_frame(void *data) run_demo(&gui); glViewport(0, 0, width, height); device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); + /* zahnrad code */ menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); menu_display_ctl(MENU_DISPLAY_CTL_UNSET_VIEWPORT, NULL); @@ -1497,6 +1500,7 @@ static void wimp_layout(wimp_handle_t *wimp) static void *wimp_init(void **userdata) { + settings_t *settings = config_get_ptr(); wimp_handle_t *wimp = NULL; menu_handle_t *menu = (menu_handle_t*) calloc(1, sizeof(*menu)); @@ -1517,11 +1521,14 @@ static void *wimp_init(void **userdata) wimp_layout(wimp); wimp_allocate_white_texture(wimp); - settings_t *settings = config_get_ptr(); + /* zahnrad code + just init the font_path variable for the + drawing function */ fill_pathname_join(font_path, settings->assets_directory, "wimp", sizeof(font_path)); fill_pathname_join(font_path, font_path, "Roboto-Regular.ttf", sizeof(font_path)); + /* zahnrad code */ return menu; error: @@ -1536,12 +1543,13 @@ static void wimp_free(void *data) if (!wimp) return; - - + + /* zahnrad code */ free(font.glyphs); zr_free(&gui.ctx); zr_buffer_free(&device.cmds); device_shutdown(&device); + /* zahnrad code */ gfx_coord_array_free(&wimp->list_block.carr); From a236c559f062533e8d1e129b7ca23ac87674af21 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 18:32:36 -0500 Subject: [PATCH 11/26] pass viewport size --- menu/drivers/wimp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 5f75f97093..e29c66db5b 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -82,10 +82,10 @@ struct wimp { struct zr_memory_status status; }; -static void wimp_demo(struct zr_context *ctx) +static void wimp_main(struct zr_context *ctx, int width, int height) { struct zr_panel layout; - if (zr_begin(ctx, &layout, "Show", zr_rect(100, 100, 200, 200), + if (zr_begin(ctx, &layout, "Show", zr_rect(width / 2, 0, width/2, height), ZR_WINDOW_BORDER|ZR_WINDOW_MOVABLE|ZR_WINDOW_SCALABLE| ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_TITLE)) { @@ -109,7 +109,7 @@ static void wimp_demo(struct zr_context *ctx) zr_end(ctx); } -static int run_demo(struct wimp *gui) +static int wimp_start(struct wimp *gui, int width, int height) { int ret = 1; static int init = 0; @@ -119,7 +119,7 @@ static int run_demo(struct wimp *gui) init = 1; } - wimp_demo(ctx); + wimp_main(ctx, width, height); zr_buffer_info(&gui->status, &gui->ctx.memory); return ret; } @@ -1406,7 +1406,7 @@ static void wimp_frame(void *data) zr_font_default_glyph_ranges()); zr_init(&gui.ctx, &alloc, &usrfnt); device_init(&device); - run_demo(&gui); + wimp_start(&gui, width, height); glViewport(0, 0, width, height); device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); /* zahnrad code */ From 9109ea3dde73aca41e728df5556b7f150b4d09cc Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 19:54:24 -0500 Subject: [PATCH 12/26] make the window half of the GLUI window for now --- menu/drivers/wimp.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index e29c66db5b..10b50be78e 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -85,10 +85,9 @@ struct wimp { static void wimp_main(struct zr_context *ctx, int width, int height) { struct zr_panel layout; - if (zr_begin(ctx, &layout, "Show", zr_rect(width / 2, 0, width/2, height), - ZR_WINDOW_BORDER|ZR_WINDOW_MOVABLE|ZR_WINDOW_SCALABLE| - ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_TITLE)) - { + if (zr_begin(ctx, &layout, "Show", zr_rect(width/2, 0, width/2, height), + ZR_WINDOW_BORDER)) + { enum {EASY, HARD}; static int op = EASY; static int property = 20; From 80284d760fecfa0955bb4795a5f2ebc0f6af0ebf Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 20:04:31 -0500 Subject: [PATCH 13/26] make the window half of the GLUI window for now --- menu/drivers/wimp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 10b50be78e..368db69a81 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -1408,6 +1408,7 @@ static void wimp_frame(void *data) wimp_start(&gui, width, height); glViewport(0, 0, width, height); device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); + zr_input_motion(&gui, menu_input_mouse_state(MENU_MOUSE_X_AXIS), menu_input_mouse_state(MENU_MOUSE_Y_AXIS)); /* zahnrad code */ menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); From 96f1cb41756007e42e173a40462308c6d017236b Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 20:16:24 -0500 Subject: [PATCH 14/26] try to implement mouse --- menu/drivers/wimp.c | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 368db69a81..ed22cd084e 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -85,7 +85,7 @@ struct wimp { static void wimp_main(struct zr_context *ctx, int width, int height) { struct zr_panel layout; - if (zr_begin(ctx, &layout, "Show", zr_rect(width/2, 0, width/2, height), + if (zr_begin(ctx, &layout, "Show", zr_rect(0, 0, width, height), ZR_WINDOW_BORDER)) { enum {EASY, HARD}; @@ -1386,14 +1386,7 @@ static void wimp_frame(void *data) wimp->box_message[0] = '\0'; } - if (settings->menu.mouse.enable && (settings->video.fullscreen - || !video_driver_ctl(RARCH_DISPLAY_CTL_HAS_WINDOWED, NULL))) - { - int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); - } /* zahnrad code */ glViewport(0, 0, width, height); @@ -1407,10 +1400,27 @@ static void wimp_frame(void *data) device_init(&device); wimp_start(&gui, width, height); glViewport(0, 0, width, height); - device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); - zr_input_motion(&gui, menu_input_mouse_state(MENU_MOUSE_X_AXIS), menu_input_mouse_state(MENU_MOUSE_Y_AXIS)); + device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); + + int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + bool left = menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON); + + printf("mouse state: %d %d %d\n", mouse_x, mouse_y, left); + zr_input_begin(&gui.ctx); + zr_input_motion(&gui.ctx, mouse_x, mouse_y); + zr_input_button(&gui.ctx, ZR_BUTTON_LEFT, mouse_x, mouse_y, left); + zr_input_end(&gui.ctx); + + /* zahnrad code */ + if (settings->menu.mouse.enable && (settings->video.fullscreen + || !video_driver_ctl(RARCH_DISPLAY_CTL_HAS_WINDOWED, NULL))) + { + wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); + } + menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); menu_display_ctl(MENU_DISPLAY_CTL_UNSET_VIEWPORT, NULL); } From e189f35cafd99879d94030dfbe0342b585c9e725 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 20:42:45 -0500 Subject: [PATCH 15/26] try to implement mouse --- menu/drivers/wimp.c | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index ed22cd084e..d19df5d2d6 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -95,6 +95,7 @@ static void wimp_main(struct zr_context *ctx, int width, int height) zr_layout_row_static(ctx, 30, 80, 1); if (zr_button_text(ctx, "button", ZR_BUTTON_DEFAULT)) { /* event handling */ + printf("pressed\n"); } zr_layout_row_dynamic(ctx, 30, 2); if (zr_option(ctx, "easy", op == EASY)) @@ -448,6 +449,29 @@ int width = 0, height = 0; struct zr_user_font usrfnt; struct zr_allocator alloc; + +static void wimp_input_motion(struct zr_context *ctx) +{ + int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + + zr_input_motion(ctx, mouse_x, mouse_y); +} + +static void wimp_input_button(struct zr_context *ctx) +{ + int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); + + if (menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)) + { + zr_input_button(ctx, ZR_BUTTON_LEFT, mouse_x, mouse_y, 1); + printf("Mouse X: %d Y: %d, LEFT: %d\n", mouse_x, mouse_y, menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)); + } + else if (menu_input_mouse_state(MENU_MOUSE_RIGHT_BUTTON)) + zr_input_button(ctx, ZR_BUTTON_RIGHT, mouse_x, mouse_y, 1); +} + /* zahnrad code */ enum @@ -1402,22 +1426,20 @@ static void wimp_frame(void *data) glViewport(0, 0, width, height); device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); - int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - bool left = menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON); - - printf("mouse state: %d %d %d\n", mouse_x, mouse_y, left); + + //printf("mouse state: %d %d %d\n", mouse_x, mouse_y, left); zr_input_begin(&gui.ctx); - zr_input_motion(&gui.ctx, mouse_x, mouse_y); - zr_input_button(&gui.ctx, ZR_BUTTON_LEFT, mouse_x, mouse_y, left); + wimp_input_motion(&gui.ctx); + wimp_input_button(&gui.ctx); zr_input_end(&gui.ctx); - - + /* zahnrad code */ if (settings->menu.mouse.enable && (settings->video.fullscreen || !video_driver_ctl(RARCH_DISPLAY_CTL_HAS_WINDOWED, NULL))) { + int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); + int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); } From dfbe4cc0499db6064a121495c3d6b3a5623d3c87 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 21:01:27 -0500 Subject: [PATCH 16/26] positioning seems ok now, click is not --- deps/zahnrad/zahnrad.c | 1 + menu/drivers/wimp.c | 39 +++++++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/deps/zahnrad/zahnrad.c b/deps/zahnrad/zahnrad.c index 12245a1499..663e075523 100644 --- a/deps/zahnrad/zahnrad.c +++ b/deps/zahnrad/zahnrad.c @@ -5938,6 +5938,7 @@ zr_input_button(struct zr_context *ctx, enum zr_buttons id, int x, int y, int do btn = &in->mouse.buttons[id]; btn->clicked_pos.x = (float)x; btn->clicked_pos.y = (float)y; + //printf("x:%d y:%d id:%d %d\n ", x , y, id, down); btn->down = down; btn->clicked++; } diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index d19df5d2d6..d27765f75d 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -72,6 +72,8 @@ #define WINDOW_WIDTH 1200 #define WINDOW_HEIGHT 800 +static int z =0 ; + /* zahnrad code */ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; @@ -85,11 +87,12 @@ struct wimp { static void wimp_main(struct zr_context *ctx, int width, int height) { struct zr_panel layout; - if (zr_begin(ctx, &layout, "Show", zr_rect(0, 0, width, height), + + if (zr_begin(ctx, &layout, "Show", zr_rect(0, 0, width/2, height), ZR_WINDOW_BORDER)) { enum {EASY, HARD}; - static int op = EASY; + static int op = HARD; static int property = 20; zr_layout_row_static(ctx, 30, 80, 1); @@ -466,7 +469,6 @@ static void wimp_input_button(struct zr_context *ctx) if (menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)) { zr_input_button(ctx, ZR_BUTTON_LEFT, mouse_x, mouse_y, 1); - printf("Mouse X: %d Y: %d, LEFT: %d\n", mouse_x, mouse_y, menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)); } else if (menu_input_mouse_state(MENU_MOUSE_RIGHT_BUTTON)) zr_input_button(ctx, ZR_BUTTON_RIGHT, mouse_x, mouse_y, 1); @@ -1413,26 +1415,21 @@ static void wimp_frame(void *data) /* zahnrad code */ - glViewport(0, 0, width, height); - alloc.userdata.ptr = NULL; - alloc.alloc = mem_alloc; - alloc.free = mem_free; - zr_buffer_init(&device.cmds, &alloc, 1024); - usrfnt = font_bake_and_upload(&device, &font, font_path, 14, - zr_font_default_glyph_ranges()); - zr_init(&gui.ctx, &alloc, &usrfnt); - device_init(&device); - wimp_start(&gui, width, height); - glViewport(0, 0, width, height); - device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); - + //printf("mouse state: %d %d %d\n", mouse_x, mouse_y, left); zr_input_begin(&gui.ctx); wimp_input_motion(&gui.ctx); wimp_input_button(&gui.ctx); zr_input_end(&gui.ctx); + wimp_start(&gui, width, height); + glViewport(0, 0, width, height); + device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); + + + + /* zahnrad code */ if (settings->menu.mouse.enable && (settings->video.fullscreen @@ -1560,6 +1557,16 @@ static void *wimp_init(void **userdata) "wimp", sizeof(font_path)); fill_pathname_join(font_path, font_path, "Roboto-Regular.ttf", sizeof(font_path)); + + glViewport(0, 0, width, height); + alloc.userdata.ptr = NULL; + alloc.alloc = mem_alloc; + alloc.free = mem_free; + zr_buffer_init(&device.cmds, &alloc, 1024); + usrfnt = font_bake_and_upload(&device, &font, font_path, 14, + zr_font_default_glyph_ranges()); + zr_init(&gui.ctx, &alloc, &usrfnt); + device_init(&device); /* zahnrad code */ return menu; From f00f09a549bf106153122a23c3694ee3ac3f96bf Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 21:12:06 -0500 Subject: [PATCH 17/26] fix input click --- deps/zahnrad/zahnrad.c | 1 - menu/drivers/wimp.c | 13 +++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/deps/zahnrad/zahnrad.c b/deps/zahnrad/zahnrad.c index 663e075523..12245a1499 100644 --- a/deps/zahnrad/zahnrad.c +++ b/deps/zahnrad/zahnrad.c @@ -5938,7 +5938,6 @@ zr_input_button(struct zr_context *ctx, enum zr_buttons id, int x, int y, int do btn = &in->mouse.buttons[id]; btn->clicked_pos.x = (float)x; btn->clicked_pos.y = (float)y; - //printf("x:%d y:%d id:%d %d\n ", x , y, id, down); btn->down = down; btn->clicked++; } diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index d27765f75d..a57879d912 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -465,13 +465,9 @@ static void wimp_input_button(struct zr_context *ctx) { int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - - if (menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)) - { - zr_input_button(ctx, ZR_BUTTON_LEFT, mouse_x, mouse_y, 1); - } - else if (menu_input_mouse_state(MENU_MOUSE_RIGHT_BUTTON)) - zr_input_button(ctx, ZR_BUTTON_RIGHT, mouse_x, mouse_y, 1); + + zr_input_button(ctx, ZR_BUTTON_LEFT, mouse_x, mouse_y, menu_input_mouse_state(MENU_MOUSE_LEFT_BUTTON)); + zr_input_button(ctx, ZR_BUTTON_RIGHT, mouse_x, mouse_y, menu_input_mouse_state(MENU_MOUSE_RIGHT_BUTTON)); } /* zahnrad code */ @@ -1415,9 +1411,6 @@ static void wimp_frame(void *data) /* zahnrad code */ - - - //printf("mouse state: %d %d %d\n", mouse_x, mouse_y, left); zr_input_begin(&gui.ctx); wimp_input_motion(&gui.ctx); wimp_input_button(&gui.ctx); From 97f2a2dfb1b26a535e492d5e8d5b8262406da384 Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 21:44:03 -0500 Subject: [PATCH 18/26] add a settings --- menu/drivers/wimp.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index a57879d912..532b9b47b3 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -87,6 +87,9 @@ struct wimp { static void wimp_main(struct zr_context *ctx, int width, int height) { struct zr_panel layout; + settings_t *settings = config_get_ptr(); + + int show_fps = settings->fps_show ? 0 : 1; if (zr_begin(ctx, &layout, "Show", zr_rect(0, 0, width/2, height), ZR_WINDOW_BORDER)) @@ -101,13 +104,10 @@ static void wimp_main(struct zr_context *ctx, int width, int height) printf("pressed\n"); } zr_layout_row_dynamic(ctx, 30, 2); - if (zr_option(ctx, "easy", op == EASY)) - op = EASY; - if (zr_option(ctx, "hard", op == HARD)) - op = HARD; - - zr_layout_row_dynamic(ctx, 22, 1); - zr_property_int(ctx, "Compression:", 0, &property, 100, 10, 1); + if (zr_checkbox(ctx, "Show fps", &show_fps)) + { + settings->fps_show = !settings->fps_show; + } } zr_end(ctx); } From 7f1f345066a8b11cf208b9843383935b8d016c8e Mon Sep 17 00:00:00 2001 From: radius Date: Sun, 14 Feb 2016 22:48:21 -0500 Subject: [PATCH 19/26] change the windows to the other side --- menu/drivers/wimp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 532b9b47b3..89b922ee19 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -91,7 +91,7 @@ static void wimp_main(struct zr_context *ctx, int width, int height) int show_fps = settings->fps_show ? 0 : 1; - if (zr_begin(ctx, &layout, "Show", zr_rect(0, 0, width/2, height), + if (zr_begin(ctx, &layout, "Show", zr_rect(width/2, 0, width/2, height), ZR_WINDOW_BORDER)) { enum {EASY, HARD}; From 4158248b7748052410fa47a2a9a6589a9e3d2280 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 16:08:36 -0500 Subject: [PATCH 20/26] add boolean wrapper thanks to Alcaro --- command_event.c | 2 +- menu/drivers/wimp.c | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/command_event.c b/command_event.c index 307ca12cef..bd00e37262 100644 --- a/command_event.c +++ b/command_event.c @@ -386,7 +386,7 @@ static void event_init_controllers(void) if (set_controller) { pad.device = device; - pad.port = i + 1; + pad.port = i; core_ctl(CORE_CTL_RETRO_SET_CONTROLLER_PORT_DEVICE, &pad); } } diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 89b922ee19..430fce7ebe 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -1,6 +1,7 @@ /* RetroArch - A frontend for libretro. * Copyright (C) 2011-2016 - Daniel De Matteis * Copyright (C) 2014-2015 - Jean-André Santoni + * Copyright (C) 2016 - Andrés Suárez * * RetroArch 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 Found- @@ -74,6 +75,14 @@ static int z =0 ; +bool zr_checkbox_bool(struct zr_context* cx, const char* text, bool *active) +{ + int x = *active; + bool ret = zr_checkbox(cx, text, &x); + *active = x; + return ret; +} + /* zahnrad code */ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; @@ -86,13 +95,12 @@ struct wimp { static void wimp_main(struct zr_context *ctx, int width, int height) { + settings_t *settings = config_get_ptr(); + struct zr_panel layout; - settings_t *settings = config_get_ptr(); - - int show_fps = settings->fps_show ? 0 : 1; - - if (zr_begin(ctx, &layout, "Show", zr_rect(width/2, 0, width/2, height), - ZR_WINDOW_BORDER)) + if (zr_begin(ctx, &layout, "Demo Window", zr_rect(width/4,height/4,width/2,height/2), + ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_MOVABLE| + ZR_WINDOW_SCALABLE|ZR_WINDOW_BORDER)) { enum {EASY, HARD}; static int op = HARD; @@ -104,10 +112,7 @@ static void wimp_main(struct zr_context *ctx, int width, int height) printf("pressed\n"); } zr_layout_row_dynamic(ctx, 30, 2); - if (zr_checkbox(ctx, "Show fps", &show_fps)) - { - settings->fps_show = !settings->fps_show; - } + zr_checkbox_bool(ctx, "Show fps", &(settings->fps_show)); } zr_end(ctx); } From 8a6c2568defc20bc23a0a1efe0e727c91ff12474 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 16:16:12 -0500 Subject: [PATCH 21/26] remove glui mouse support --- menu/drivers/wimp.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 430fce7ebe..653f8e54b9 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -901,17 +901,6 @@ static void wimp_render(void *data) menu_input_ctl(MENU_INPUT_CTL_POINTER_ACCEL_WRITE, &new_accel_val); } - if (settings->menu.mouse.enable) - { - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - - unsigned new_pointer_val = - (mouse_y - wimp->line_height + wimp->scroll_y - 16) - / wimp->line_height; - - menu_input_ctl(MENU_INPUT_CTL_MOUSE_PTR, &new_pointer_val); - } - if (wimp->scroll_y < 0) wimp->scroll_y = 0; From 4c738b964516267f3a496b27367a94a130c2f1d6 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 16:47:50 -0500 Subject: [PATCH 22/26] add volume slider --- menu/drivers/wimp.c | 49 ++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 653f8e54b9..9e391116e6 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -97,22 +97,30 @@ static void wimp_main(struct zr_context *ctx, int width, int height) { settings_t *settings = config_get_ptr(); - struct zr_panel layout; - if (zr_begin(ctx, &layout, "Demo Window", zr_rect(width/4,height/4,width/2,height/2), - ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_MOVABLE| - ZR_WINDOW_SCALABLE|ZR_WINDOW_BORDER)) + struct zr_panel layout; + if (zr_begin(ctx, &layout, "Demo Window", zr_rect(10, 10, width/2, 400), + ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_MOVABLE| + ZR_WINDOW_SCALABLE|ZR_WINDOW_BORDER)) { - enum {EASY, HARD}; - static int op = HARD; - static int property = 20; - - zr_layout_row_static(ctx, 30, 80, 1); - if (zr_button_text(ctx, "button", ZR_BUTTON_DEFAULT)) { - /* event handling */ - printf("pressed\n"); - } zr_layout_row_dynamic(ctx, 30, 2); - zr_checkbox_bool(ctx, "Show fps", &(settings->fps_show)); + if (zr_button_text(ctx, "Quit", ZR_BUTTON_DEFAULT)) { + /* event handling */ + printf("Pressed Event\n"); + rarch_ctl(RARCH_CTL_FORCE_QUIT, NULL); + } + if (zr_button_text(ctx, "Quit", ZR_BUTTON_DEFAULT)) { + /* event handling */ + printf("Pressed Event\n"); + rarch_ctl(RARCH_CTL_FORCE_QUIT, NULL); + } + zr_layout_row_dynamic(ctx, 30, 4); + zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); + zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); + zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); + zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); + zr_layout_row_dynamic(ctx, 30, 1); + zr_slider_float(ctx, 0, &settings->audio.volume, 100, 5); + } zr_end(ctx); } @@ -1402,8 +1410,6 @@ static void wimp_frame(void *data) wimp->box_message[0] = '\0'; } - - /* zahnrad code */ zr_input_begin(&gui.ctx); wimp_input_motion(&gui.ctx); @@ -1414,19 +1420,8 @@ static void wimp_frame(void *data) glViewport(0, 0, width, height); device_draw(&device, &gui.ctx, width, height, ZR_ANTI_ALIASING_ON); - - - /* zahnrad code */ - if (settings->menu.mouse.enable && (settings->video.fullscreen - || !video_driver_ctl(RARCH_DISPLAY_CTL_HAS_WINDOWED, NULL))) - { - int16_t mouse_x = menu_input_mouse_state(MENU_MOUSE_X_AXIS); - int16_t mouse_y = menu_input_mouse_state(MENU_MOUSE_Y_AXIS); - wimp_draw_cursor(wimp, &white_bg[0], mouse_x, mouse_y, width, height); - } - menu_display_ctl(MENU_DISPLAY_CTL_RESTORE_CLEAR_COLOR, NULL); menu_display_ctl(MENU_DISPLAY_CTL_UNSET_VIEWPORT, NULL); } From ba43724fecef072d2cd2304463830401d82f3b4f Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 17:13:06 -0500 Subject: [PATCH 23/26] fix slider --- menu/drivers/wimp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 9e391116e6..0863ab0cd4 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -119,7 +119,7 @@ static void wimp_main(struct zr_context *ctx, int width, int height) zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); zr_layout_row_dynamic(ctx, 30, 1); - zr_slider_float(ctx, 0, &settings->audio.volume, 100, 5); + zr_slider_float(ctx, -80, &settings->audio.volume, 12, 0.5); } zr_end(ctx); From 777be51eb65a665a7e44d9edd9e07a77cb120afb Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 21:55:34 -0500 Subject: [PATCH 24/26] add a few more elements --- menu/drivers/wimp.c | 215 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 212 insertions(+), 3 deletions(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 0863ab0cd4..32919fb37e 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -86,6 +86,197 @@ bool zr_checkbox_bool(struct zr_context* cx, const char* text, bool *active) /* zahnrad code */ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; +static void set_style(struct zr_context *ctx, enum theme theme) +{ + if (theme == THEME_WHITE) { + ctx->style.colors[ZR_COLOR_TEXT] = zr_rgba(70, 70, 70, 255); + ctx->style.colors[ZR_COLOR_TEXT_HOVERING] = zr_rgba(10, 10, 10, 255); + ctx->style.colors[ZR_COLOR_TEXT_ACTIVE] = zr_rgba(20, 20, 20, 255); + ctx->style.colors[ZR_COLOR_WINDOW] = zr_rgba(175, 175, 175, 255); + ctx->style.colors[ZR_COLOR_HEADER] = zr_rgba(175, 175, 175, 255); + ctx->style.colors[ZR_COLOR_BORDER] = zr_rgba(0, 0, 0, 255); + ctx->style.colors[ZR_COLOR_BUTTON] = zr_rgba(185, 185, 185, 255); + ctx->style.colors[ZR_COLOR_BUTTON_HOVER] = zr_rgba(170, 170, 170, 255); + ctx->style.colors[ZR_COLOR_BUTTON_ACTIVE] = zr_rgba(160, 160, 160, 255); + ctx->style.colors[ZR_COLOR_TOGGLE] = zr_rgba(150, 150, 150, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_HOVER] = zr_rgba(120, 120, 120, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_CURSOR] = zr_rgba(175, 175, 175, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_HOVER] = zr_rgba(150, 150, 150, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_TEXT] = zr_rgba(70, 70, 70, 255); + ctx->style.colors[ZR_COLOR_SLIDER] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR] = zr_rgba(80, 80, 80, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_HOVER] = zr_rgba(70, 70, 70, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_ACTIVE] = zr_rgba(60, 60, 60, 255); + ctx->style.colors[ZR_COLOR_PROGRESS] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR] = zr_rgba(80, 80, 80, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_HOVER] = zr_rgba(70, 70, 70, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_ACTIVE] = zr_rgba(60, 60, 60, 255); + ctx->style.colors[ZR_COLOR_PROPERTY] = zr_rgba(175, 175, 175, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_HOVER] = zr_rgba(160, 160, 160, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_ACTIVE] = zr_rgba(165, 165, 165, 255); + ctx->style.colors[ZR_COLOR_INPUT] = zr_rgba(150, 150, 150, 255); + ctx->style.colors[ZR_COLOR_INPUT_CURSOR] = zr_rgba(0, 0, 0, 255); + ctx->style.colors[ZR_COLOR_INPUT_TEXT] = zr_rgba(0, 0, 0, 255); + ctx->style.colors[ZR_COLOR_COMBO] = zr_rgba(175, 175, 175, 255); + ctx->style.colors[ZR_COLOR_HISTO] = zr_rgba(160, 160, 160, 255); + ctx->style.colors[ZR_COLOR_HISTO_BARS] = zr_rgba(45, 45, 45, 255); + ctx->style.colors[ZR_COLOR_HISTO_HIGHLIGHT] = zr_rgba( 255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_PLOT] = zr_rgba(160, 160, 160, 255); + ctx->style.colors[ZR_COLOR_PLOT_LINES] = zr_rgba(45, 45, 45, 255); + ctx->style.colors[ZR_COLOR_PLOT_HIGHLIGHT] = zr_rgba(255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR] = zr_rgba(180, 180, 180, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR] = zr_rgba(140, 140, 140, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_HOVER] = zr_rgba(150, 150, 150, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE] = zr_rgba(160, 160, 160, 255); + ctx->style.colors[ZR_COLOR_TABLE_LINES] = zr_rgba(100, 100, 100, 255); + ctx->style.colors[ZR_COLOR_TAB_HEADER] = zr_rgba(180, 180, 180, 255); + ctx->style.colors[ZR_COLOR_SCALER] = zr_rgba(100, 100, 100, 255); + } else if (theme == THEME_RED) { + ctx->style.rounding[ZR_ROUNDING_SCROLLBAR] = 0; + ctx->style.properties[ZR_PROPERTY_SCROLLBAR_SIZE] = zr_vec2(10,10); + ctx->style.colors[ZR_COLOR_TEXT] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_TEXT_HOVERING] = zr_rgba(195, 195, 195, 255); + ctx->style.colors[ZR_COLOR_TEXT_ACTIVE] = zr_rgba(200, 200, 200, 255); + ctx->style.colors[ZR_COLOR_WINDOW] = zr_rgba(30, 33, 40, 215); + ctx->style.colors[ZR_COLOR_HEADER] = zr_rgba(181, 45, 69, 220); + ctx->style.colors[ZR_COLOR_BORDER] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_BUTTON] = zr_rgba(181, 45, 69, 255); + ctx->style.colors[ZR_COLOR_BUTTON_HOVER] = zr_rgba(190, 50, 70, 255); + ctx->style.colors[ZR_COLOR_BUTTON_ACTIVE] = zr_rgba(195, 55, 75, 255); + ctx->style.colors[ZR_COLOR_TOGGLE] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_HOVER] = zr_rgba(45, 60, 60, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_CURSOR] = zr_rgba(181, 45, 69, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE] = zr_rgba(181, 45, 69, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_HOVER] = zr_rgba(181, 45, 69, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_TEXT] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_SLIDER] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR] = zr_rgba(181, 45, 69, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_HOVER] = zr_rgba(186, 50, 74, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_ACTIVE] = zr_rgba(191, 55, 79, 255); + ctx->style.colors[ZR_COLOR_PROGRESS] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR] = zr_rgba(181, 45, 69, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_HOVER] = zr_rgba(186, 50, 74, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_ACTIVE] = zr_rgba(191, 55, 79, 255); + ctx->style.colors[ZR_COLOR_PROPERTY] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_HOVER] = zr_rgba(55, 60, 72, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_ACTIVE] = zr_rgba(60, 65, 77, 255); + ctx->style.colors[ZR_COLOR_INPUT] = zr_rgba(51, 55, 67, 225); + ctx->style.colors[ZR_COLOR_INPUT_CURSOR] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_INPUT_TEXT] = zr_rgba(190, 190, 190, 255); + ctx->style.colors[ZR_COLOR_COMBO] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_HISTO] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_HISTO_BARS] = zr_rgba(170, 40, 60, 255); + ctx->style.colors[ZR_COLOR_HISTO_HIGHLIGHT] = zr_rgba( 255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_PLOT] = zr_rgba(51, 55, 67, 255); + ctx->style.colors[ZR_COLOR_PLOT_LINES] = zr_rgba(170, 40, 60, 255); + ctx->style.colors[ZR_COLOR_PLOT_HIGHLIGHT] = zr_rgba(255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR] = zr_rgba(30, 33, 40, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR] = zr_rgba(64, 84, 95, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_HOVER] = zr_rgba(70, 90, 100, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE] = zr_rgba(75, 95, 105, 255); + ctx->style.colors[ZR_COLOR_TABLE_LINES] = zr_rgba(100, 100, 100, 255); + ctx->style.colors[ZR_COLOR_TAB_HEADER] = zr_rgba(181, 45, 69, 220); + ctx->style.colors[ZR_COLOR_SCALER] = zr_rgba(100, 100, 100, 255); + } else if (theme == THEME_BLUE) { + ctx->style.rounding[ZR_ROUNDING_SCROLLBAR] = 0; + ctx->style.properties[ZR_PROPERTY_SCROLLBAR_SIZE] = zr_vec2(10,10); + ctx->style.colors[ZR_COLOR_TEXT] = zr_rgba(20, 20, 20, 255); + ctx->style.colors[ZR_COLOR_TEXT_HOVERING] = zr_rgba(195, 195, 195, 255); + ctx->style.colors[ZR_COLOR_TEXT_ACTIVE] = zr_rgba(200, 200, 200, 255); + ctx->style.colors[ZR_COLOR_WINDOW] = zr_rgba(202, 212, 214, 215); + ctx->style.colors[ZR_COLOR_HEADER] = zr_rgba(137, 182, 224, 220); + ctx->style.colors[ZR_COLOR_BORDER] = zr_rgba(140, 159, 173, 255); + ctx->style.colors[ZR_COLOR_BUTTON] = zr_rgba(137, 182, 224, 255); + ctx->style.colors[ZR_COLOR_BUTTON_HOVER] = zr_rgba(142, 187, 229, 255); + ctx->style.colors[ZR_COLOR_BUTTON_ACTIVE] = zr_rgba(147, 192, 234, 255); + ctx->style.colors[ZR_COLOR_TOGGLE] = zr_rgba(177, 210, 210, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_HOVER] = zr_rgba(245, 245, 245, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_CURSOR] = zr_rgba(142, 187, 229, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE] = zr_rgba(147, 192, 234, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_HOVER] = zr_rgba(150, 150, 150, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_TEXT] = zr_rgba(70, 70, 70, 255); + ctx->style.colors[ZR_COLOR_SLIDER] = zr_rgba(177, 210, 210, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR] = zr_rgba(137, 182, 224, 245); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_HOVER] = zr_rgba(142, 188, 229, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_ACTIVE] = zr_rgba(147, 193, 234, 255); + ctx->style.colors[ZR_COLOR_PROGRESS] = zr_rgba(177, 210, 210, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR] = zr_rgba(137, 182, 224, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_HOVER] = zr_rgba(142, 188, 229, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_ACTIVE] = zr_rgba(147, 193, 234, 255); + ctx->style.colors[ZR_COLOR_PROPERTY] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_HOVER] = zr_rgba(235, 235, 235, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_ACTIVE] = zr_rgba(230, 230, 230, 255); + ctx->style.colors[ZR_COLOR_INPUT] = zr_rgba(210, 210, 210, 225); + ctx->style.colors[ZR_COLOR_INPUT_CURSOR] = zr_rgba(20, 20, 20, 255); + ctx->style.colors[ZR_COLOR_INPUT_TEXT] = zr_rgba(20, 20, 20, 255); + ctx->style.colors[ZR_COLOR_COMBO] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_HISTO] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_HISTO_BARS] = zr_rgba(137, 182, 224, 255); + ctx->style.colors[ZR_COLOR_HISTO_HIGHLIGHT] = zr_rgba( 255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_PLOT] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_PLOT_LINES] = zr_rgba(137, 182, 224, 255); + ctx->style.colors[ZR_COLOR_PLOT_HIGHLIGHT] = zr_rgba(255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR] = zr_rgba(190, 200, 200, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR] = zr_rgba(64, 84, 95, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_HOVER] = zr_rgba(70, 90, 100, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE] = zr_rgba(75, 95, 105, 255); + ctx->style.colors[ZR_COLOR_TABLE_LINES] = zr_rgba(100, 100, 100, 255); + ctx->style.colors[ZR_COLOR_TAB_HEADER] = zr_rgba(156, 193, 220, 255); + ctx->style.colors[ZR_COLOR_SCALER] = zr_rgba(100, 100, 100, 255); + } else if (theme == THEME_DARK) { + ctx->style.rounding[ZR_ROUNDING_SCROLLBAR] = 0; + ctx->style.properties[ZR_PROPERTY_SCROLLBAR_SIZE] = zr_vec2(10,10); + ctx->style.colors[ZR_COLOR_TEXT] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_TEXT_HOVERING] = zr_rgba(195, 195, 195, 255); + ctx->style.colors[ZR_COLOR_TEXT_ACTIVE] = zr_rgba(200, 200, 200, 255); + ctx->style.colors[ZR_COLOR_WINDOW] = zr_rgba(45, 53, 56, 215); + ctx->style.colors[ZR_COLOR_HEADER] = zr_rgba(46, 46, 46, 220); + ctx->style.colors[ZR_COLOR_BORDER] = zr_rgba(46, 46, 46, 255); + ctx->style.colors[ZR_COLOR_BUTTON] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_BUTTON_HOVER] = zr_rgba(53, 88, 116, 255); + ctx->style.colors[ZR_COLOR_BUTTON_ACTIVE] = zr_rgba(58, 93, 121, 255); + ctx->style.colors[ZR_COLOR_TOGGLE] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_HOVER] = zr_rgba(55, 63, 66, 255); + ctx->style.colors[ZR_COLOR_TOGGLE_CURSOR] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_HOVER] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_SELECTABLE_TEXT] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_SLIDER] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR] = zr_rgba(48, 83, 111, 245); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_HOVER] = zr_rgba(53, 88, 116, 255); + ctx->style.colors[ZR_COLOR_SLIDER_CURSOR_ACTIVE] = zr_rgba(58, 93, 121, 255); + ctx->style.colors[ZR_COLOR_PROGRESS] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_HOVER] = zr_rgba(53, 88, 116, 255); + ctx->style.colors[ZR_COLOR_PROGRESS_CURSOR_ACTIVE] = zr_rgba(58, 93, 121, 255); + ctx->style.colors[ZR_COLOR_PROPERTY] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_HOVER] = zr_rgba(55, 63, 66, 255); + ctx->style.colors[ZR_COLOR_PROPERTY_ACTIVE] = zr_rgba(60, 68, 71, 255); + ctx->style.colors[ZR_COLOR_INPUT] = zr_rgba(50, 58, 61, 225); + ctx->style.colors[ZR_COLOR_INPUT_CURSOR] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_INPUT_TEXT] = zr_rgba(210, 210, 210, 255); + ctx->style.colors[ZR_COLOR_COMBO] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_HISTO] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_HISTO_BARS] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_HISTO_HIGHLIGHT] = zr_rgba(255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_PLOT] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_PLOT_LINES] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_PLOT_HIGHLIGHT] = zr_rgba(255, 0, 0, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR] = zr_rgba(50, 58, 61, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_HOVER] = zr_rgba(53, 88, 116, 255); + ctx->style.colors[ZR_COLOR_SCROLLBAR_CURSOR_ACTIVE] = zr_rgba(58, 93, 121, 255); + ctx->style.colors[ZR_COLOR_TABLE_LINES] = zr_rgba(100, 100, 100, 255); + ctx->style.colors[ZR_COLOR_TAB_HEADER] = zr_rgba(48, 83, 111, 255); + ctx->style.colors[ZR_COLOR_SCALER] = zr_rgba(100, 100, 100, 255); + } else { + zr_load_default_style(ctx, ZR_DEFAULT_ALL); + } +} + + + struct wimp { void *memory; struct zr_context ctx; @@ -93,7 +284,7 @@ struct wimp { struct zr_memory_status status; }; -static void wimp_main(struct zr_context *ctx, int width, int height) +static void wimp_main(struct zr_context *ctx, int width, int height, struct wimp *gui) { settings_t *settings = config_get_ptr(); @@ -118,8 +309,26 @@ static void wimp_main(struct zr_context *ctx, int width, int height) zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); zr_checkbox_bool(ctx, "Show FPS", &(settings->fps_show)); - zr_layout_row_dynamic(ctx, 30, 1); + zr_layout_row_dynamic(ctx, 30, 2); + zr_label(ctx, "Volume:", ZR_TEXT_LEFT); zr_slider_float(ctx, -80, &settings->audio.volume, 12, 0.5); + zr_layout_row_dynamic(ctx, 30, 1); + zr_property_int(ctx, "Max Users:", 1, (int*)&(settings->input.max_users), MAX_USERS, 1, 1); + + struct zr_panel combo; + static const char *themes[] = {"Black", "White", "Red", "Blue", "Dark", "Grey"}; + enum theme old = gui->theme; + if (zr_combo_begin_text(ctx, &combo, themes[gui->theme], 300)) { + zr_layout_row_dynamic(ctx, 25, 1); + gui->theme = zr_combo_item(ctx, themes[THEME_BLACK], ZR_TEXT_CENTERED) ? THEME_BLACK : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_WHITE], ZR_TEXT_CENTERED) ? THEME_WHITE : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_RED], ZR_TEXT_CENTERED) ? THEME_RED : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_BLUE], ZR_TEXT_CENTERED) ? THEME_BLUE : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_DARK], ZR_TEXT_CENTERED) ? THEME_DARK : gui->theme; + if (old != gui->theme) set_style(ctx, gui->theme); + zr_combo_end(ctx); + } + } zr_end(ctx); @@ -135,7 +344,7 @@ static int wimp_start(struct wimp *gui, int width, int height) init = 1; } - wimp_main(ctx, width, height); + wimp_main(ctx, width, height, gui); zr_buffer_info(&gui->status, &gui->ctx.memory); return ret; } From 1e74f02b94061e9b46bba2e73ebc5c692ac40399 Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 22:36:15 -0500 Subject: [PATCH 25/26] add control window from the demo --- menu/drivers/wimp.c | 102 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index 32919fb37e..dcfc1d31d0 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -83,6 +83,17 @@ bool zr_checkbox_bool(struct zr_context* cx, const char* text, bool *active) return ret; } +static void zr_labelf(struct zr_context *ctx, enum zr_text_align align, const char *fmt, ...) +{ + char buffer[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + buffer[1023] = 0; + zr_label(ctx, buffer, align); + va_end(args); +} + /* zahnrad code */ enum theme {THEME_BLACK, THEME_WHITE, THEME_RED, THEME_BLUE, THEME_DARK}; @@ -284,6 +295,95 @@ struct wimp { struct zr_memory_status status; }; +static int wimp_control(struct zr_context *ctx, int width, int height, struct wimp *gui) +{ + int i; + struct zr_panel layout; + if (zr_begin(ctx, &layout, "Control", zr_rect(900, 10, 350, 520), + ZR_WINDOW_CLOSABLE|ZR_WINDOW_MINIMIZABLE|ZR_WINDOW_MOVABLE| + ZR_WINDOW_SCALABLE|ZR_WINDOW_BORDER)) + { + /* Style */ + if (zr_layout_push(ctx, ZR_LAYOUT_TAB, "Metrics", ZR_MINIMIZED)) + { + zr_layout_row_dynamic(ctx, 20, 2); + zr_label(ctx,"Total:", ZR_TEXT_LEFT); + zr_labelf(ctx, ZR_TEXT_LEFT, "%lu", gui->status.size); + zr_label(ctx,"Used:", ZR_TEXT_LEFT); + zr_labelf(ctx, ZR_TEXT_LEFT, "%lu", gui->status.allocated); + zr_label(ctx,"Required:", ZR_TEXT_LEFT); + zr_labelf(ctx, ZR_TEXT_LEFT, "%lu", gui->status.needed); + zr_label(ctx,"Calls:", ZR_TEXT_LEFT); + zr_labelf(ctx, ZR_TEXT_LEFT, "%lu", gui->status.calls); + zr_layout_pop(ctx); + } + if (zr_layout_push(ctx, ZR_LAYOUT_TAB, "Properties", ZR_MINIMIZED)) + { + zr_layout_row_dynamic(ctx, 22, 3); + for (i = 0; i <= ZR_PROPERTY_SCROLLBAR_SIZE; ++i) + { + zr_label(ctx, zr_get_property_name((enum zr_style_properties)i), ZR_TEXT_LEFT); + zr_property_float(ctx, "#X:", 0, &ctx->style.properties[i].x, 20, 1, 1); + zr_property_float(ctx, "#Y:", 0, &ctx->style.properties[i].y, 20, 1, 1); + } + zr_layout_pop(ctx); + } + if (zr_layout_push(ctx, ZR_LAYOUT_TAB, "Rounding", ZR_MINIMIZED)) + { + zr_layout_row_dynamic(ctx, 22, 2); + for (i = 0; i < ZR_ROUNDING_MAX; ++i) + { + zr_label(ctx, zr_get_rounding_name((enum zr_style_rounding)i), ZR_TEXT_LEFT); + zr_property_float(ctx, "#R:", 0, &ctx->style.rounding[i], 20, 1, 1); + } + zr_layout_pop(ctx); + } + if (zr_layout_push(ctx, ZR_LAYOUT_TAB, "Color", ZR_MINIMIZED)) + { + struct zr_panel tab, combo; + enum theme old = gui->theme; + static const char *themes[] = {"Black", "White", "Red", "Blue", "Dark", "Grey"}; + + zr_layout_row_dynamic(ctx, 25, 2); + zr_label(ctx, "THEME:", ZR_TEXT_LEFT); + if (zr_combo_begin_text(ctx, &combo, themes[gui->theme], 300)) + { + zr_layout_row_dynamic(ctx, 25, 1); + gui->theme = zr_combo_item(ctx, themes[THEME_BLACK], ZR_TEXT_CENTERED) ? THEME_BLACK : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_WHITE], ZR_TEXT_CENTERED) ? THEME_WHITE : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_RED], ZR_TEXT_CENTERED) ? THEME_RED : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_BLUE], ZR_TEXT_CENTERED) ? THEME_BLUE : gui->theme; + gui->theme = zr_combo_item(ctx, themes[THEME_DARK], ZR_TEXT_CENTERED) ? THEME_DARK : gui->theme; + if (old != gui->theme) set_style(ctx, gui->theme); + zr_combo_end(ctx); + } + + zr_layout_row_dynamic(ctx, 300, 1); + if (zr_group_begin(ctx, &tab, "Colors", 0)) + { + for (i = 0; i < ZR_COLOR_COUNT; ++i) + { + zr_layout_row_dynamic(ctx, 25, 2); + zr_label(ctx, zr_get_color_name((enum zr_style_colors)i), ZR_TEXT_LEFT); + if (zr_combo_begin_color(ctx, &combo, ctx->style.colors[i], 200)) + { + zr_layout_row_dynamic(ctx, 25, 1); + ctx->style.colors[i].r = (zr_byte)zr_propertyi(ctx, "#R:", 0, ctx->style.colors[i].r, 255, 1,1); + ctx->style.colors[i].g = (zr_byte)zr_propertyi(ctx, "#G:", 0, ctx->style.colors[i].g, 255, 1,1); + ctx->style.colors[i].b = (zr_byte)zr_propertyi(ctx, "#B:", 0, ctx->style.colors[i].b, 255, 1,1); + ctx->style.colors[i].a = (zr_byte)zr_propertyi(ctx, "#A:", 0, ctx->style.colors[i].a, 255, 1,1); + zr_combo_end(ctx); + } + } + zr_group_end(ctx); + } + zr_layout_pop(ctx); + } + } + zr_end(ctx); + return !zr_window_is_closed(ctx, "Control"); +} + static void wimp_main(struct zr_context *ctx, int width, int height, struct wimp *gui) { settings_t *settings = config_get_ptr(); @@ -344,7 +444,9 @@ static int wimp_start(struct wimp *gui, int width, int height) init = 1; } + wimp_main(ctx, width, height, gui); + wimp_control(ctx, width, height, gui); zr_buffer_info(&gui->status, &gui->ctx.memory); return ret; } From ee6a0e40b0460a1bcd493f43b65fce3b0450ef8e Mon Sep 17 00:00:00 2001 From: radius Date: Wed, 24 Feb 2016 22:58:11 -0500 Subject: [PATCH 26/26] set rounding to zero, elements look better like this --- menu/drivers/wimp.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/menu/drivers/wimp.c b/menu/drivers/wimp.c index dcfc1d31d0..45c78f55ef 100644 --- a/menu/drivers/wimp.c +++ b/menu/drivers/wimp.c @@ -444,7 +444,11 @@ static int wimp_start(struct wimp *gui, int width, int height) init = 1; } - + /* set rounding to zero on all elements */ + for (int i = 0; i < ZR_ROUNDING_MAX; ++i) + { + ctx->style.rounding[i] = 0; + } wimp_main(ctx, width, height, gui); wimp_control(ctx, width, height, gui); zr_buffer_info(&gui->status, &gui->ctx.memory);