Compare commits

...

17 Commits

  1. 41
      .clang-format
  2. 5
      .gitignore
  3. 184
      ARCHITECTURE.md
  4. 15
      Makefile
  5. 4
      RENDERER
  6. 211
      cache.c
  7. 5
      compile_flags.txt
  8. 40
      def_style.h
  9. 1
      fm/README
  10. 193
      fm/libconf.c
  11. 7
      fm/libconf.h
  12. 3
      font-to-atlas/.gitignore
  13. 2
      font-to-atlas/Makefile
  14. 118
      font-to-atlas/ff.c
  15. 25
      font-to-atlas/ff.h
  16. 107
      font-to-atlas/main.c
  17. BIN
      font-to-atlas/monospace.ttf
  18. 5077
      font-to-atlas/stb_truetype.h
  19. 8
      get_raylib.sh
  20. 40
      input.c
  21. 2
      opengl-ren/.gitignore
  22. 5
      opengl-ren/Makefile
  23. 2
      opengl-ren/README
  24. BIN
      opengl-ren/charmap.ff
  25. BIN
      opengl-ren/charmap.png
  26. 12
      opengl-ren/fragment.glsl
  27. 521
      opengl-ren/main.c
  28. 17
      opengl-ren/vertex.glsl
  29. 2
      opengl/.gitignore
  30. 5
      opengl/Makefile
  31. 2
      opengl/README
  32. 10
      opengl/fragment.glsl
  33. 278
      opengl/main.c
  34. 14
      opengl/vertex.glsl
  35. 126
      rewrite/generic_stack.h
  36. 3
      stuff/generic_hash.h
  37. 0
      stuff/generic_stack.h
  38. 214
      stuff/main.c
  39. 348
      stuff/vectree.h
  40. 11
      test/Makefile
  41. 268
      test/main.c
  42. BIN
      test/monospace.ttf
  43. 623
      test/stb_rect_pack.h
  44. 5077
      test/stb_truetype.h
  45. 212
      test/stbttf.h
  46. 4
      text_rendering/.gitignore
  47. 18
      text_rendering/Makefile
  48. 8
      text_rendering/box_fragshader.glsl
  49. 19
      text_rendering/box_vertshader.glsl
  50. 195
      text_rendering/font.c
  51. 50
      text_rendering/font.h
  52. 18
      text_rendering/font_fragshader.glsl
  53. 26
      text_rendering/font_vertshader.glsl
  54. 126
      text_rendering/generic_cache.h
  55. 46
      text_rendering/genmake.sh
  56. 96
      text_rendering/main.c
  57. 826
      text_rendering/ren.c
  58. 54
      text_rendering/ren.h
  59. 1724
      text_rendering/stb_image_write.h
  60. 5077
      text_rendering/stb_truetype.h
  61. 126
      text_rendering/util.c
  62. 34
      text_rendering/util.h
  63. 69
      timer.c
  64. 16
      timer.h
  65. 2054
      ugui.c
  66. 352
      ugui.h
  67. 355
      vectree.c

@ -0,0 +1,41 @@
# linux kernel style formatting
BasedOnStyle: LLVM
IndentWidth: 8
UseTab: AlignWithSpaces
BreakBeforeBraces: Linux
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false
ColumnLimit: 85
InsertBraces: true
SortIncludes: Never
BinPackParameters: false
BinPackArguments: false
Cpp11BracedListStyle: true
SpaceBeforeCpp11BracedList: true
SeparateDefinitionBlocks: Always
AlignAfterOpenBracket: BlockIndent
InsertNewlineAtEOF: true
AlignConsecutiveDeclarations:
Enabled: true
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: true
PadOperators: false
AlignConsecutiveMacros:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignConsecutiveBitFields:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true

5
.gitignore vendored

@ -1,5 +1,8 @@
microgui
ugui
*.o
test/test
**/compile_commands.json
**/.cache
**/.cache
test
raylib/*

@ -0,0 +1,184 @@
## High level overview
Under the hood every element has an id, this id allows the library to store state
between frames.
Elements are also cached such that when the ui tree is rebuilt at the beginning of
every frame the element data structure doesn't have to be rebuilt.
Elements are arranged in a tree, nodes are container elements that can contain other
elements, leafs are elements that cannot contain other elements.
Every element has a size and a position, containers also have to keep track of their
layout information and some other state.
Elements can push commands into the draw stack, which is a structure that contains
all the draw commands that the user application has to perform do display the ui
correctly, such commands include drawing lines, rectangles, sprites, text, etc.
```text
+-----------+
| ug_init() |
+-----+-----+
|
|
|
+---------v----------+
|ug_input_keyboard() |
|ug_input_mouse() <----+
|ug_input_clipboard()| |
| ... | |
+---------+----------+ |
| |
| |
+-------v--------+ |
|ug_frame_begin()| |
+-------+--------+ |
| |
| |
+---------v----------+ |
|ug_window_start() | |
+---->ug_container_start()| |
| |ug_div_start() | |
| | ... | |
| +---------+----------+ |
| | |
| | |
multiple +--------v---------+ |
times |ug_layout_row() | |
| |ug_layout_column()| |
| |ug_layout_float() | |
| | ... | |
| +--------+---------+ |
| | |
| | |
| +------v------+ |
| |ug_button() | |
| |ug_text_box()| |
| |ug_slider() | |
| | ... | |
| +------+------+ |
| | |
+--------------+ |
| |
+--------v---------+ |
|ug_window_end() | |
|ug_container_end()| |
|ug_div_end() | |
| ... | |
+--------+---------+ |
| |
| |
| |
+------v-------+ |
|ug_frame_end()| |
+------+-------+ |
| |
| |
| |
+------v-------+ |
|user draws the| |
| ui +-------+
+------+-------+
|
|quit
|
+------v-------+
| ug_destroy() |
+--------------+
```
### Layouting
Layouting happens in a dynamic grid, when a new element is inserted in a non-floating
manner it reserves a space in the grid, new elements are placed following this grid.
Every div has two points of origin, one for the row layout and one for the column
layout, named origin_r and origin_c respectively
origin_r is used when the row layout is used and it is used to position the child
elements one next to the other, as such it always points to the top-right edge
of the last row element
```text
Layout: row
#: lost space
Parent div
x---------------------------------+
|[origin_c] |
|[origin_r] |
| |
| |
| |
| |
| |
| |
| |
+---------------------------------+
Parent div
+-----x---------------------------+
| |[origin_r] |
| E1 | |
| | |
x-----+---------------------------+
|[origin_c] |
| | |
| | |
| | |
| | |
| | |
+-----+---------------------------+
Parent div
+-----+----------+-----x----------+
| | E2 | |[origin_r]|
| E1 +----------+ | |
| |##########| E3 | |
+-----+##########| | |
|################| | |
+----------------x-----+----------+
| [origin_c] |
| | |
| | |
| | |
+----------------+----------------+
```
TODO: handle when the content overflows the div
- Use a different concept, like a view or relative space, for example the child
div could have position `[0,0]` but in reality it is relative to the origin of the
parent div
- each div could have a view and a total area of the content, when drawing everything
is clipped to the view and scrollbars are shown
- individual elements accept dimensions and the x/y coordinates could be interpreted
as offset if the layout is row/column or absolute coordinates if the leayout is floating
A div can be marked resizeable or fixed, and static or dynamic. The difference being
that resizeable adds a resize handle to the div and dynamic lets the content overflow
causing scrollbars to be drawn
### Notes
How elements determine if they have focus or not
```C
// in begin_{container} code
calculate focus
set has_focus property
// in the element code
if(PARENT_HAS_FOCUS()) {
update stuff
} else {
fast path to return
}
```
How to get ids:
1. use a name for each element
2. supply an id for each element
3. use a macro and the line position as id and then hash it
4. use a macro, get the code line and hash it

@ -0,0 +1,15 @@
CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/include
CC = gcc
LDFLAGS = -Lraylib/lib -lm
all: ugui
ugui: ugui.o vectree.o cache.o timer.o raylib/lib/libraylib.a
ugui.o: ugui.c ugui.h
vectree.o: vectree.c ugui.h
cache.o: cache.c ugui.h
timer.o: timer.c timer.h

@ -1,4 +0,0 @@
Divide up the OpenGL renderer into three parts, one part that draws simple shapes,
another that only draws text and one that is responsible for drawing icons and
mapping sprites in general, this way all the texture atlases are separate and
everything is done within 3 draw calls

@ -0,0 +1,211 @@
// LRU cache:
/*
* The cache uses a pool (array) containing all the elements and a hash table
* associating the position in the pool with the element id
*/
#include <stdlib.h>
#include <string.h>
#include "ugui.h"
// To have less collisions TABLE_SIZE has to be larger than the cache size
#define CACHE_SIZE 265
#define TABLE_SIZE (CACHE_SIZE * 1.5f)
#define CACHE_NCYCLES (CACHE_SIZE * 2 / 3)
#define CACHE_BSIZE (((CACHE_SIZE + 0x3f) & (~0x3f)) >> 6)
#define CACHE_BRESET(b) \
for (int i = 0; i < CACHE_BSIZE; b[i++] = 0) \
;
#define CACHE_BSET(b, x) b[(x) >> 6] |= (uint64_t)1 << ((x)&63)
#define CACHE_BTEST(b, x) (b[(x) >> 6] & ((uint64_t)1 << ((x)&63)))
/* Hash Table Implementation ----------------------------------------------------- */
#define HASH_MAXSIZE 4096
// hash table (id -> cache index)
typedef struct {
UgId id;
uint32_t index;
} IdElem;
typedef struct _IdTable {
uint32_t items, size, exp;
IdElem bucket[];
} IdTable;
IdTable *table_create(uint32_t size)
{
if (!size || size > HASH_MAXSIZE) {
return NULL;
}
/* round to the greater power of two */
/* FIXME: check for intger overflow here */
uint32_t exp = 32 - __builtin_clz(size - 1);
size = 1 << (exp);
/* FIXME: check for intger overflow here */
IdTable *ht = malloc(sizeof(IdTable) + sizeof(IdElem) * size);
if (ht) {
ht->items = 0;
ht->size = size;
memset(ht->bucket, 0, sizeof(IdTable) * size);
}
return ht;
}
void table_destroy(IdTable *ht)
{
if (ht) {
free(ht);
}
}
// Find and return the element by pointer
IdElem *table_search(IdTable *ht, UgId id)
{
if (!ht) {
return NULL;
}
// In this case id is the hash
uint32_t idx = id % ht->size;
for (uint32_t x = 0, i; x < ht->size; x++) {
i = (idx + x) % ht->size;
if (ht->bucket[i].id == 0 || ht->bucket[i].id == id) {
return &(ht->bucket[i]);
}
}
return NULL;
}
// FIXME: this simply overrides the found item
IdElem *table_insert(IdTable *ht, IdElem entry)
{
IdElem *r = table_search(ht, entry.id);
if (r != NULL) {
if (r->id != 0) {
ht->items++;
}
*r = entry;
}
return r;
}
IdElem *table_remove(IdTable *ht, UgId id)
{
if (!ht) {
return NULL;
}
IdElem *r = table_search(ht, id);
if (r) {
r->id = 0;
}
return r;
}
/* Cache Implementation ---------------------------------------------------------- */
// Every CACHE_CYCLES operations mark not-present the unused elements
#define CACHE_CYCLE(c) \
do { \
if (++(c->cycles) > CACHE_NCYCLES) { \
for (int i = 0; i < CACHE_BSIZE; i++) { \
c->present[i] &= c->used[i]; \
c->used[i] = 0; \
} \
c->cycles = 0; \
} \
} while (0)
/* FIXME: check for allocation errors */
UgElemCache ug_cache_init(void)
{
IdTable *t = table_create(TABLE_SIZE);
UgElem *a = malloc(sizeof(UgElem) * CACHE_SIZE);
uint64_t *p = malloc(sizeof(uint64_t) * CACHE_BSIZE);
uint64_t *u = malloc(sizeof(uint64_t) * CACHE_BSIZE);
CACHE_BRESET(p);
CACHE_BRESET(u);
return (UgElemCache) {.table = t, .array = a, .present = p, .used = u, 0};
}
void ug_cache_free(UgElemCache *cache)
{
if (cache) {
table_destroy(cache->table);
free(cache->array);
free(cache->present);
free(cache->used);
}
}
UgElem *ug_cache_search(UgElemCache *cache, UgId id)
{
if (!cache) {
return NULL;
}
IdElem *r;
r = table_search(cache->table, id);
/* MISS */
if (!r || id != r->id) {
return NULL;
}
/* MISS, the data is not valid (not present) */
if (!CACHE_BTEST(cache->present, r->index)) {
return NULL;
}
/* HIT, set as recently used */
CACHE_BSET(cache->used, r->index);
return (&cache->array[r->index]);
}
/* Look for a free spot in the present bitmap and return its index */
/* If there is no free space left then just return the first position */
int ug_cache_get_free_spot(UgElemCache *cache)
{
if (!cache) {
return -1;
}
int x = 0;
for (int b = 0; b < CACHE_BSIZE; b++) {
if (cache->present[b] == 0) {
x = 64;
} else {
x = __builtin_clzll(cache->present[b]);
}
x = 64 - x;
if (!CACHE_BTEST(cache->present, x + 64 * b)) {
return x + 64 * b;
}
}
return 0;
}
UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index)
{
if (!cache) {
return NULL;
}
UgElem *spot = NULL;
/* Set used and present */
CACHE_BSET(cache->present, index);
CACHE_BSET(cache->used, index);
CACHE_CYCLE(cache);
spot = &(cache->array[index]);
*spot = *g;
IdElem e = {.id = g->id, .index = index};
if (!table_insert(cache->table, e)) {
return NULL;
}
return spot;
}
// Insert an element in the cache
UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index)
{
*index = ug_cache_get_free_spot(cache);
return ug_cache_insert_at(cache, g, *index);
}

@ -0,0 +1,5 @@
-Iraylib/include
-Wall
-Wextra
-pedantic
-std=c11

@ -1,40 +0,0 @@
#ifndef _UG_DEF_STYLE_H
#define _UG_DEF_STYLE_H
#include "ugui.h"
#define SZ_INT(x) x.size.i
static const ug_style_t default_style = {
.color = {
.bg = RGB_FORMAT(0x131313),
.fg = RGB_FORMAT(0xffffff),
},
.margin = SIZE_PX(3),
.border = {
.color = RGB_FORMAT(0xf50a00),
.size = SIZE_PX(2),
},
.title = {
.color = {
.bg = RGB_FORMAT(0xbbbbbb),
.fg = RGB_FORMAT(0xffff00),
},
.height = SIZE_PX(20),
.font_size = SIZE_PX(14),
},
.btn = {
.color = {
.act = RGB_FORMAT(0x440044),
.bg = RGB_FORMAT(0x006600),
.fg = RGB_FORMAT(0xffff00),
.sel = RGB_FORMAT(0x0a0aff),
.br = RGB_FORMAT(0xff00ff),
},
.font_size = SIZE_PX(10),
.border = SIZE_PX(5),
},
};
#endif

@ -0,0 +1 @@
File Manager using ugui

@ -0,0 +1,193 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <linux/limits.h>
#include <sys/stat.h>
#include <sys/types.h>
#define PATHS_NO 3
static char temp[PATH_MAX] = {0};
static int valid_paths = 0;
static char paths[PATHS_NO][PATH_MAX] = {0}; // 0: xdg, 1: fallback, 2: global
static const mode_t CONFDIR_MODE = S_IRWXU | S_IRWXU | S_IRWXU | S_IRWXU;
static void err(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[libconf]: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
// implements "mkdir -p"
static int mkdir_p(const char *path)
{
char tmp_path[PATH_MAX] = {0};
strncpy(tmp_path, path, PATH_MAX - 1);
struct stat st;
for (char *tk = strtok(tmp_path, "/"); tk != NULL; tk = strtok(NULL, "/")) {
if (tk != tmp_path) {
tk[-1] = '/';
}
if (stat(tmp_path, &st)) {
if (errno != ENOENT) {
err("could not stat() %s: %s",
tmp_path,
strerror(errno));
return 1;
}
if (mkdir(tmp_path, CONFDIR_MODE)) {
err("could not mkdir() %s: %s",
tmp_path,
strerror(errno));
return 1;
}
} else if (!S_ISDIR(st.st_mode)) {
err("could not create directory %s: file exists but it is "
"not a directory",
tmp_path);
return 1;
}
}
return 0;
}
// TODO: add a way to add a custom directory to the paths, for example via an
// environment variable
// TODO: maybe add a LIBCONF_PATH that overrides the defaults
/* default paths are:
* 1. ${XDG_CONFIG_HOME}/libconf/appid.d/
* 2. ${HOME}/.config/libconf/appid.d/
* 3. etc/libconf/appid.d/
*/
static int fill_paths(const char *id)
{
// TODO: verify id
if (id == NULL) {
err("must provide a valid app id");
return 1;
}
const char *xdg = getenv("XDG_CONFIG_HOME");
if (xdg != NULL) {
snprintf(paths[0], PATH_MAX, "%s/libconf/%s.d", xdg, id);
}
const char *home = getenv("HOME");
if (home) {
snprintf(temp, PATH_MAX, "%s/.config/libconf/%s.d", home, id);
if (mkdir_p(temp) == 0) {
strcpy(paths[1], temp);
}
}
// do not create global config path since most likely we don't have
// the necessary permissions to do so
snprintf(paths[2], PATH_MAX, "/etc/libconf/%s.d", id);
for (size_t i = 0; i < PATHS_NO; i++) {
printf("paths[%ld] = %s\n", i, paths[i]);
}
valid_paths = 1;
return 0;
}
// get config file path
const char *lcnf_get_conf_file(const char *id, const char *name, int global)
{
static char str[PATH_MAX] = {0};
if (id == NULL) {
return NULL;
}
if (!valid_paths) {
if (fill_paths(id)) {
return NULL;
}
}
// quick path for global config file
if (global && paths[2][0] != '\0') {
snprintf(str, PATH_MAX, "%s/%s", paths[2], name);
return str;
}
int found = 0;
for (size_t i = 0; i < PATHS_NO; i++) {
if (paths[i][0] == '\0') {
continue;
}
struct stat st;
snprintf(str, PATH_MAX, "%s/%s", paths[i], name);
if (stat(str, &st) && errno != ENOENT) {
err("could not stat %s: %s", str, strerror(errno));
}
if (S_ISREG(st.st_mode)) {
found = 1;
break;
}
}
return found ? str : NULL;
}
// get config file directory path
const char *lcnf_get_conf_dir(const char *id, int global)
{
if (id == NULL) {
return NULL;
}
if (global) {
return paths[2][0] != '\0' ? paths[2] : NULL;
}
if (paths[0][0] != '\0') {
return paths[0];
} else if (paths[1][0] != '\0') {
return paths[1];
} else {
return NULL;
}
}
// TODO: watch directory for file updates
int lcnf_watch_conf_dir(const char *id, int global);
// TODO: collect all files into a temporary one, useful for when you have multiple
// configuration files like 00-foo.conf, 01-bar.conf and 99-baz.conf
const char *lcnf_collect_conf_files(const char *id, int global);
#if 1
int main(void)
{
const char *p = lcnf_get_conf_file("pippo", "pippo.toml", 0);
if (p) {
printf("config found: %s\n", p);
} else {
printf("config not found\n");
}
return 0;
}
#endif

@ -0,0 +1,7 @@
#ifndef LIBCONF_H_
#define LIBCONF_H_
const char *lcnf_get_conf_file(const char *id, const char *name, int global);
const char *lcnf_get_conf_dir(const char *id, int global);
#endif

@ -1,3 +0,0 @@
*.ff
*.png
font-to-atlas

@ -1,2 +0,0 @@
font-to-atlas: main.c ff.c ff.h
cc -lm -g main.c ff.c -o font-to-atlas

@ -1,118 +0,0 @@
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "ff.h"
#define MAX(a,b) a>b?a:b
struct ff * ff_new(uint32_t width, uint32_t height)
{
uint64_t size = (uint64_t)width*height*sizeof(struct ff);
struct ff *image = malloc(sizeof(struct ff) + size);
if (!image)
return NULL;
memcpy(image->magic, "farbfeld", 8);
image->width = htonl(width);
image->height = htonl(height);
// create a transparent image
memset(image->pixels, 0, size);
return image;
}
uint64_t ff_bytes(const struct ff *image)
{
if (!image)
return 0;
return sizeof(struct ff)+(uint64_t)ntohl(image->width)*ntohl(image->height)*sizeof(struct ff_px);
}
struct ff * ff_resize(struct ff *image, uint32_t width, uint32_t height)
{
struct ff *new = NULL;
int64_t size = sizeof(struct ff)+(int64_t)width*height*sizeof(uint64_t);
if (image) {
int64_t old_size = ff_bytes(image);
if (old_size == size)
return image;
new = realloc(image, size);
if (!new)
return NULL;
uint32_t old_width = ntohl(new->width);
uint32_t old_height = ntohl(new->height);
if (size-old_size > 0) {
struct ff_px *b = new->pixels;
memset(&b[old_width*old_height], 0, size-old_size);
for (int64_t c = (int64_t)old_height-1; c >= 0; c--) {
memmove(&b[width*c], &b[old_width*c], sizeof(struct ff_px)*old_width);
memset(&b[c*old_width], 0, sizeof(struct ff_px)*(c*width-c*old_width));
}
} else {
}
new->height = htonl(height);
new->width = htonl(width);
} else {
new = ff_new(width, height);
}
return new;
}
int ff_verify (const struct ff *image)
{
if (!image || strncmp("farbfeld", (char*)image->magic, 8))
return 1;
return 0;
}
int ff_free(struct ff *image)
{
if (!ff_verify(image))
free(image);
return 0;
}
// overlays the bitmap containing only 1 8bpp channel to the image starting at (x,y)
// be stands for the data is already big endian
int ff_overlay_8r(struct ff **image, const uint8_t *bitmap, uint32_t x, uint32_t y, uint32_t w, uint32_t h)
{
if (!image || !*image)
return 1;
uint32_t iw = ntohl((*image)->width), ih = ntohl((*image)->height);
*image = ff_resize(*image, MAX(iw, x+w), MAX(ih, y+h));
if (!image)
return -1;
iw = ntohl((*image)->width);
ih = ntohl((*image)->height);
for (uint32_t r = 0; r < h; r++) {
for (uint32_t c = 0; c < w; c++) {
uint8_t col = bitmap[r*w+c];
struct ff_px p = {
.r = 0xffff,
.g = 0xffff,
.b = 0xffff,
.a = htons(257*col)
};
(*image)->pixels[(r+y)*iw + (c+x)] = p;
}
}
return 0;
}

@ -1,25 +0,0 @@
#ifndef _FARBFELD_EZ_H
#define _FARBFELD_EZ_H
#include <stdint.h>
struct __attribute__((packed)) ff_px { uint16_t r, g, b, a; };
struct __attribute__((packed)) ff {
int8_t magic[8];
uint32_t width, height;
struct ff_px pixels[];
};
struct ff * ff_new(uint32_t width, uint32_t height);
int ff_verify (const struct ff *image);
int ff_free(struct ff *image);
uint64_t ff_bytes(const struct ff *image);
struct ff * ff_resize(struct ff *image, uint32_t width, uint32_t height);
int ff_overlay_8r(struct ff **image, const uint8_t *bitmap, uint32_t x, uint32_t y, uint32_t w, uint32_t h);
#endif

@ -1,107 +0,0 @@
#define _POSIX_C_SOURCE 200809l
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#include <sys/mman.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include "stb_truetype.h"
#include "ff.h"
const int font_size = 32;
void map_file(const unsigned char **str, int *size, const char *path)
{
if (!path)
err(EXIT_FAILURE, "NULL filename");
FILE *fp = fopen(path, "r");
if (!fp)
err(EXIT_FAILURE, "Cannot open file %s", path);
*size = lseek(fileno(fp), 0, SEEK_END);
if (*size == (off_t)-1)
err(EXIT_FAILURE, "lseek failed");
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
if (*str == (void*)-1)
err(EXIT_FAILURE, "mmap failed");
if (fclose(fp))
err(EXIT_FAILURE, "Error closing file");
}
int main(int argc, char *argv[])
{
if (argc < 2)
return EXIT_FAILURE;
int len;
const unsigned char *map;
map_file(&map, &len, argv[1]);
stbtt_fontinfo font;
stbtt_InitFont(&font, map, stbtt_GetFontOffsetForIndex(map, 0));
// all this is to get the font bounding box in pixels
float font_scale = 1.0;
font_scale = stbtt_ScaleForPixelHeight(&font, font_size);
int ascent, descent, linegap;
stbtt_GetFontVMetrics(&font, &ascent, &descent, &linegap);
int x0,y0,x1,y1;
int bound_w, bound_h;
stbtt_GetFontBoundingBox(&font, &x0, &y0, &x1, &y1);
printf("font_scale: %f\n", font_scale);
printf("x0:%d y0:%d x1:%d y1:%d\n",x0,y0,x1,y1);
int baseline = font_scale * -y0;
bound_h = (baseline+font_scale*y1) - (baseline+font_scale*y0);
bound_w = (font_scale*x1) - (font_scale*x0);
baseline = bound_h - baseline;
unsigned char *bitmap = malloc(bound_h*bound_w);
if (!bitmap)
err(EXIT_FAILURE, "Cannot allocate bitmap");
printf("bounding h:%d w:%d\n", bound_h, bound_w);
printf("baseline: %d\n", baseline);
struct ff *image = ff_new(0, 0);
// get all ascii
int x = 0, y = 0, maxwidth = 64*bound_w;
for (unsigned int i = 0; i <= 0x7F; i++) {
int x0,y0,x1,y1,w,h,l,a, ox,oy;
int g = stbtt_FindGlyphIndex(&font, i);
stbtt_GetGlyphBitmapBoxSubpixel(&font, g, font_scale, font_scale, 0, 0, &x0, &y0, &x1, &y1);
w = x1 - x0;
h = y1 - y0;
//printf("%d\n", y0);
stbtt_GetGlyphHMetrics(&font, g, &a, &l);
stbtt_MakeGlyphBitmapSubpixel(&font, bitmap, w, h, w, font_scale, font_scale, 0, 0, g);
//printf("'%c' -> l*scale:%.0f, y0:%d\n", i, font_scale*l, bound_h+y0);
ox = font_scale*l;
oy = bound_h+y0;
ff_overlay_8r(&image, bitmap, x+ox, y+oy, w, h);
x += bound_w;
if (x >= maxwidth) y += bound_h;
x %= maxwidth;
}
FILE *fp = fopen("out.ff", "w");
if (fp) {
fwrite(image, 1, ff_bytes(image), fp);
fclose(fp);
}
free(bitmap);
ff_free(image);
munmap((void *)map, len);
return 0;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,8 @@
#!/bin/sh
mkdir -p raylib
wget https://github.com/raysan5/raylib/releases/download/5.0/raylib-5.0_linux_amd64.tar.gz
tar -xvf raylib-5.0_linux_amd64.tar.gz
mv ./raylib-5.0_linux_amd64/* ./raylib/
rm -rf raylib-5.0_linux_amd64 raylib-5.0_linux_amd64.tar.gz

@ -1,40 +0,0 @@
#include "ugui.h"
/*=============================================================================*
* Input Handling *
*=============================================================================*/
#define TEST_CTX(ctx) { if (!ctx) return -1; }
int ug_input_mousemove(ug_ctx_t *ctx, int x, int y)
{
TEST_CTX(ctx)
if (x < 0 || y < 0)
return 0;
ctx->mouse.pos = (ug_vec2_t){.x = x, .y = y};
return 0;
}
int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask)
{
TEST_CTX(ctx);
ctx->mouse.update |= mask;
return 0;
}
int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask)
{
TEST_CTX(ctx);
ctx->mouse.update |= mask;
return 0;
}

@ -1,2 +0,0 @@
a.out
gl

@ -1,5 +0,0 @@
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
LDFLAGS = -lSDL2 -lGLEW -lGL
gl: main.c
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl

@ -1,2 +0,0 @@
since I've never worked with opengl I made this folder as a little test environment
for an SDL-OpenGL renderer

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

@ -1,12 +0,0 @@
#version 330
flat in vec4 out_color;
in vec2 texture_coord;
uniform sampler2D texture_sampler;
out vec4 color;
void main()
{
color = texture(texture_sampler, texture_coord) + out_color;
}

@ -1,521 +0,0 @@
#define _POSIX_C_SOURCE 200809l
#include <sys/mman.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <SDL2/SDL_opengl.h>
#define GLSL_VERT_SHADER "vertex.glsl"
#define GLSL_FRAG_SHADER "fragment.glsl"
#define PACKED __attribute__((packed))
const int vertindex = 0;
const int colindex = 1;
const int textindex = 2;
struct {
SDL_Window *w;
SDL_GLContext *gl;
GLuint gl_vertbuffer;
GLuint gl_program;
GLuint font_texture;
} ren = {0};
typedef struct PACKED {
union { GLfloat x, u; };
union { GLfloat y, v; };
} vec2;
typedef struct PACKED {
union { GLfloat x, r; };
union { GLfloat y, g; };
union { GLfloat z, b; };
union { GLfloat w, a; };
} vec4;
// a vertex has a position and a color
struct PACKED vertex {
vec2 pos;
vec2 texture;
vec4 color;
};
int w_height(SDL_Window *);
int w_width(SDL_Window *);
// this just descrives a farbfeld image
// https://tools.suckless.org/farbfeld/
struct PACKED _ff {
uint8_t magic[8];
uint32_t w, h;
uint64_t bytes[];
};
const int gw = 7, gh = 9;
unsigned int fw, fh;
struct {
struct vertex *v;
int size, idx;
int prev_idx;
unsigned long int hash, prev_hash;
} vstack = {0};
void grow_stack(int step)
{
vstack.v = realloc(vstack.v, (vstack.size+step)*sizeof(*(vstack.v)));
if(!vstack.v)
err(-1, "Could not allocate stack #S: %s", strerror(errno));
memset(&(vstack.v[vstack.size]), 0, step*sizeof(*(vstack.v)));
vstack.size += step;
}
void push(struct vertex v)
{
if (vstack.idx >= vstack.size)
grow_stack(6);
vstack.v[vstack.idx++] = v;
}
void update_hash()
{
if (!vstack.idx)
return;
unsigned int hash = 0x5400F1B3;
unsigned char *v = (unsigned char *)vstack.v;
int size = vstack.idx;
for (; size; size--) {
hash += v[size-1];
hash += hash << 10;
hash ^= hash >> 6;
}
hash += hash << 3;
hash ^= hash >> 11;
hash += hash << 15;
vstack.hash = hash;
}
void force_changed()
{
vstack.prev_idx = 0;
}
int changed()
{
return vstack.prev_idx != vstack.idx || vstack.prev_hash != vstack.hash;
}
int vstack_push_quad_c(int x, int y, int w, int h, vec4 color)
{
// x4,y4 x3,y3
// +-------------+
// |(x,y) /|
// | / |
// | 2 / |
// | / |
// | / |
// | / 1 |
// |/ |
// +-------------+
// x1,y1 x2,y2
int hw = w_width(ren.w)/2;
int hh = w_height(ren.w)/2;
float x1, x2, x3, x4;
float y1, y2, y3, y4;
x4 = x1 = (float)(x - hw) / hw;
x2 = x3 = (float)(x+w - hw) / hw;
y4 = y3 = (float)(hh - y) / hh;
y1 = y2 = (float)(hh - y-h) / hh;
push((struct vertex){ .pos.x=x1, .pos.y=y1, .color=color });
push((struct vertex){ .pos.x=x2, .pos.y=y2, .color=color });
push((struct vertex){ .pos.x=x3, .pos.y=y3, .color=color });
push((struct vertex){ .pos.x=x1, .pos.y=y1, .color=color });
push((struct vertex){ .pos.x=x3, .pos.y=y3, .color=color });
push((struct vertex){ .pos.x=x4, .pos.y=y4, .color=color });
return 0;
}
int vstack_push_quad_t(int x, int y, int w, int h, int u, int v)
{
// x4,y4 x3,y3
// +-------------+
// |(x,y) /|
// | / |
// | 2 / |
// | / |
// | / |
// | / 1 |
// |/ |
// +-------------+
// x1,y1 x2,y2
int hw = w_width(ren.w)/2;
int hh = w_height(ren.w)/2;
float x1, x2, x3, x4;
float y1, y2, y3, y4;
x1 = x4 = (float)(x - hw) / hw;
x2 = x3 = (float)(x+w - hw) / hw;
y1 = y2 = (float)(hh - y-h) / hh;
y3 = y4 = (float)(hh - y) / hh;
float u1, u2, u3, u4;
float v1, v2, v3, v4;
u1 = u4 = (float)(u) / fw;
u2 = u3 = (float)(u+gw) / fw;
v1 = v2 = (float)(v+gh) / fh;
v3 = v4 = (float)(v) / fh;
push((struct vertex){ .pos.x=x1, .pos.y=y1, .texture.x=u1, .texture.y=v1 });
push((struct vertex){ .pos.x=x2, .pos.y=y2, .texture.x=u2, .texture.y=v2 });
push((struct vertex){ .pos.x=x3, .pos.y=y3, .texture.x=u3, .texture.y=v3 });
push((struct vertex){ .pos.x=x1, .pos.y=y1, .texture.x=u1, .texture.y=v1 });
push((struct vertex){ .pos.x=x3, .pos.y=y3, .texture.x=u3, .texture.y=v3 });
push((struct vertex){ .pos.x=x4, .pos.y=y4, .texture.x=u4, .texture.y=v4 });
return 0;
}
void vstack_clear(void)
{
vstack.prev_hash = vstack.hash;
vstack.prev_idx = vstack.idx;
vstack.idx = 0;
}
// copy the vertex buffer from system to video memory
void ren_initvertbuffer()
{
// generate a buffer id
glGenBuffers(1, &ren.gl_vertbuffer);
// tell opengl that we want to work on that buffer
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
// copy the vertex data into the gpu memory, GL_STATIC_DRAW tells opengl
// that the data will be used for drawing (DRAW) and it will only be modified
// every frame (DYNAMIC)
glBufferData(GL_ARRAY_BUFFER, vstack.idx*sizeof(struct vertex), vstack.v, GL_DYNAMIC_DRAW);
// set the format of each vertex, in this case each vertex is made of 4
// coordinates, x y z w, where w is the clip coordinate, stride is
// sizeof(vec4) since every postition vertex there is a vector representing
// color data
// set the position data of the vertex buffer, and bind it as input to the
// vertex shader with an index of 0
glEnableVertexAttribArray(vertindex);
glVertexAttribPointer(vertindex, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), 0);
// set the color data of the vertex buffer to index 1
// vertex attribute is the OpenGL name given to a set of vertices which are
// given as input to a vertext shader, in a shader an array of vertices is
// always referred to by index and not by pointer or other manners, indices
// go from 0 to 15
glEnableVertexAttribArray(colindex);
glVertexAttribPointer(colindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)(2*sizeof(vec2)));
// texture uv data
glEnableVertexAttribArray(textindex);
glVertexAttribPointer(textindex, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)sizeof(vec2));
// reset the object bind so not to create errors
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void map_file(const char **str, int *size, const char *fname)
{
FILE *fp = fopen(fname, "r");
*size = lseek(fileno(fp), 0, SEEK_END);
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
fclose(fp);
}
// print shader compilation errors
int debug_shader(GLuint shader)
{
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status != GL_FALSE)
return 0;
GLint log_length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
glGetShaderInfoLog(shader, log_length, NULL, log_str);
const char *shader_type_str = NULL;
GLint shader_type;
glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type);
switch(shader_type) {
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
}
fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_str, log_str);
free(log_str);
return -1;
}
// print program compilation errors
int debug_program(GLuint prog)
{
GLint status;
glGetProgramiv (prog, GL_LINK_STATUS, &status);
if (status != GL_FALSE)
return 0;
GLint log_length;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length);
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
glGetProgramInfoLog(prog, log_length, NULL, log_str);
fprintf(stderr, "Linker failure: %s\n", log_str);
free(log_str);
return -1;
}
void ren_initshaders()
{
GLuint gl_vertshader, gl_fragshader;
// initialize the vertex shader and get the corresponding id
gl_vertshader = glCreateShader(GL_VERTEX_SHADER);
if (!gl_vertshader)
err(-1, "Could not create the vertex shader");
// map the shader file into memory
const char *vshader_str = NULL;
int vshader_size;
map_file(&vshader_str, &vshader_size, GLSL_VERT_SHADER);
// get the shader into opengl
glShaderSource(gl_vertshader, 1, &vshader_str, NULL);
// compile the shader
glCompileShader(gl_vertshader);
if (debug_shader(gl_vertshader))
exit(EXIT_FAILURE);
// do the same for the fragment shader
// FIXME: make this a function
gl_fragshader = glCreateShader(GL_FRAGMENT_SHADER);
if (!gl_fragshader)
err(-1, "Could not create the vertex shader");
// map the shader file into memory
const char *fshader_str = NULL;
int fshader_size;
map_file(&fshader_str, &fshader_size, GLSL_FRAG_SHADER);
// get the shader into opengl
glShaderSource(gl_fragshader, 1, &fshader_str, NULL);
// compile the shader
glCompileShader(gl_fragshader);
if (debug_shader(gl_fragshader))
exit(EXIT_FAILURE);
// create the main program object, it is an amalgamation of all shaders
ren.gl_program = glCreateProgram();
// attach the shaders to the program (set which shaders are present)
glAttachShader(ren.gl_program, gl_vertshader);
glAttachShader(ren.gl_program, gl_fragshader);
// then link the program (basically the linking stage of the program)
glLinkProgram(ren.gl_program);
if (debug_program(ren.gl_program))
exit(EXIT_FAILURE);
// after linking the shaders can be detached and the source freed from
// memory since the program is ready to use
glDetachShader(ren.gl_program, gl_vertshader);
glDetachShader(ren.gl_program, gl_fragshader);
munmap((void *)vshader_str, vshader_size);
munmap((void *)fshader_str, fshader_size);
// now tell opengl to use the program
glUseProgram(ren.gl_program);
}
void ren_drawvertbuffer()
{
if (!changed())
return;
glClear(GL_COLOR_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
// upload vertex data
if (vstack.idx != vstack.prev_idx)
glBufferData(GL_ARRAY_BUFFER, vstack.idx*sizeof(struct vertex), vstack.v, GL_DYNAMIC_DRAW);
else
glBufferSubData(GL_ARRAY_BUFFER, 0, vstack.idx*sizeof(struct vertex), vstack.v);
// draw vertex data
glDrawArrays(GL_TRIANGLES, 0, vstack.idx);
glBindBuffer(GL_ARRAY_BUFFER, 0);
SDL_GL_SwapWindow(ren.w);
}
int w_height(SDL_Window *win)
{
int h;
SDL_GetWindowSize(win, NULL, &h);
return h;
}
int w_width(SDL_Window *win)
{
int w;
SDL_GetWindowSize(win, &w, NULL);
return w;
}
void import_font(const char *path)
{
const char *map;
int size;
const struct _ff *img;
map_file(&map, &size, path);
img = (const struct _ff *)map;
fw = ntohl(img->w);
fh = ntohl(img->h);
glGenTextures(1, &ren.font_texture);
glBindTexture(GL_TEXTURE_2D, ren.font_texture);
// farbfeld image
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_SHORT, img->bytes);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
munmap((void *)map, size);
}
void push_text(int x, int y, float scale, const char *s)
{
for (; *s; s++) {
int u, v;
int idx = *s - ' ';
u = idx % (fw / gw);
v = (idx / (fw / gw)) % (fh / gh);
vstack_push_quad_t(x, y, gw*scale, gh*scale, u*gw, v*gh);
x += gw*scale;
if (*s == '\n')
y += gh;
}
}
int main (void)
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
ren.w = SDL_CreateWindow("test",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
500, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE );
// create the OpenGL context
ren.gl = SDL_GL_CreateContext(ren.w);
if (ren.gl == NULL)
err(-1, "Failed to create OpenGL context: %s", SDL_GetError());
// select some features
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glEnable(GL_TEXTURE_2D);
// initialize glew, this library gives us the declarations for most GL
// functions, in the future it would be cool to do it manually
GLenum glew_err = glewInit();
if (glew_err != GLEW_OK)
err(-1, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err));
ren_initshaders();
import_font("./charmap.ff");
vec4 magenta = {.r=1.0, .g=0.0, .b=1.0, .a=1.0};
ren_initvertbuffer();
glClearColor(0.3f, 0.3f, 0.3f, 0.f);
glClear(GL_COLOR_BUFFER_BIT);
// event loop and drawing
SDL_Event ev = {0};
int running = 1;
do {
SDL_WaitEvent(&ev);
switch (ev.type) {
case SDL_QUIT: running = 0; break;
case SDL_WINDOWEVENT:
if(ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
glViewport(0, 0, w_width(ren.w), w_height(ren.w));
glScissor(0, 0, w_width(ren.w), w_height(ren.w));
force_changed();
}
break;
default: break;
}
vstack_push_quad_c(0, 0, 100, 100, magenta);
vstack_push_quad_c(200, 0, 10, 10, magenta);
vstack_push_quad_c(10, 150, 100, 100, magenta);
push_text(250, 250, 1.0f, "Ciao Victoria <3");
update_hash();
ren_drawvertbuffer();
vstack_clear();
} while(running);
glUseProgram(0);
glDisableVertexAttribArray(vertindex);
glDisableVertexAttribArray(colindex);
glDeleteTextures(1, &ren.font_texture);
glDeleteBuffers(1, &ren.gl_vertbuffer);
SDL_GL_DeleteContext(ren.gl);
SDL_DestroyWindow(ren.w);
SDL_Quit();
return 0;
}

@ -1,17 +0,0 @@
#version 330
// use the input ("in") vertices from index zero
layout(location = 0) in vec2 position;
layout(location = 1) in vec4 color;
layout(location = 2) in vec2 uv;
flat out vec4 out_color;
out vec2 texture_coord;
void main()
{
// simply copy teh output position
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
out_color = color;
texture_coord = uv;
}

2
opengl/.gitignore vendored

@ -1,2 +0,0 @@
a.out
gl

@ -1,5 +0,0 @@
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
LDFLAGS = -lSDL2 -lGLEW -lGL
gl: main.c
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl

@ -1,2 +0,0 @@
since I've never worked with opengl I made this folder as a little test environment
for an SDL-OpenGL renderer

@ -1,10 +0,0 @@
#version 330
smooth in vec4 out_color;
out vec4 color;
void main()
{
// set the color for each vertex to white
color = out_color;
}

@ -1,278 +0,0 @@
#define _POSIX_C_SOURCE 200809l
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <err.h>
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <SDL2/SDL_opengl.h>
#define GLSL_VERT_SHADER "vertex.glsl"
#define GLSL_FRAG_SHADER "fragment.glsl"
#define PACKED __attribute__((packed))
const int vertindex = 0;
const int colindex = 1;
struct {
SDL_Window *w;
SDL_GLContext *gl;
GLuint gl_vertbuffer;
GLuint gl_program;
} ren = {0};
typedef struct PACKED {
union { GLfloat x, r; };
union { GLfloat y, g; };
union { GLfloat z, b; };
union { GLfloat w, a; };
} vec4;
// a vertex has a position and a color
struct PACKED vertex { vec4 pos, col; };
#define VNUM(s) (sizeof(s)/sizeof(s[0]))
struct vertex vertbuffer[] = {
{ .pos={.x= 0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
{ .pos={.x=-0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
{ .pos={.x= 0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
{ .pos={.x= 0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
{ .pos={.x=-0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
{ .pos={.x=-0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} },
};
// copy the vertex buffer from system to video memory
void ren_initvertbuffer()
{
// generate a buffer id
glGenBuffers(1, &ren.gl_vertbuffer);
// tell opengl that we want to work on that buffer
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
// copy the vertex data into the gpu memory, GL_STATIC_DRAW tells opengl
// that the data will be used for drawing (DRAW) and it will only be modified
// once (STATIC)
glBufferData(GL_ARRAY_BUFFER, sizeof(vertbuffer), &vertbuffer, GL_STATIC_DRAW);
// set the format of each vertex, in this case each vertex is made of 4
// coordinates, x y z w, where w is the clip coordinate, stride is
// sizeof(vec4) since every postition vertex there is a vector representing
// color data
// set the position data of the vertex buffer, and bind it as input to the
// vertex shader with an index of 0
glEnableVertexAttribArray(vertindex);
glVertexAttribPointer(vertindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), 0);
// set the color data of the vertex buffer to index 1
// vertex attribute is the OpenGL name given to a set of vertices which are
// given as input to a vertext shader, in a shader an array of vertices is
// always referred to by index and not by pointer or other manners, indices
// go from 0 to 15
glEnableVertexAttribArray(colindex);
glVertexAttribPointer(colindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)sizeof(vec4));
// reset the object bind so not to create errors
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
void map_file(const char **str, int *size, const char *fname)
{
FILE *fp = fopen(fname, "r");
*size = lseek(fileno(fp), 0, SEEK_END);
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
fclose(fp);
}
// print shader compilation errors
int debug_shader(GLuint shader)
{
GLint status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status != GL_FALSE)
return 0;
GLint log_length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
glGetShaderInfoLog(shader, log_length, NULL, log_str);
const char *shader_type_str = NULL;
GLint shader_type;
glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type);
switch(shader_type) {
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
}
fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_str, log_str);
free(log_str);
return -1;
}
// print program compilation errors
int debug_program(GLuint prog)
{
GLint status;
glGetProgramiv (prog, GL_LINK_STATUS, &status);
if (status != GL_FALSE)
return 0;
GLint log_length;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length);
GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar));
glGetProgramInfoLog(prog, log_length, NULL, log_str);
fprintf(stderr, "Linker failure: %s\n", log_str);
free(log_str);
return -1;
}
void ren_initshaders()
{
GLuint gl_vertshader, gl_fragshader;
// initialize the vertex shader and get the corresponding id
gl_vertshader = glCreateShader(GL_VERTEX_SHADER);
if (!gl_vertshader)
err(-1, "Could not create the vertex shader");
// map the shader file into memory
const char *vshader_str = NULL;
int vshader_size;
map_file(&vshader_str, &vshader_size, GLSL_VERT_SHADER);
// get the shader into opengl
glShaderSource(gl_vertshader, 1, &vshader_str, NULL);
// compile the shader
glCompileShader(gl_vertshader);
if (debug_shader(gl_vertshader))
exit(EXIT_FAILURE);
// do the same for the fragment shader
// FIXME: make this a function
gl_fragshader = glCreateShader(GL_FRAGMENT_SHADER);
if (!gl_fragshader)
err(-1, "Could not create the vertex shader");
// map the shader file into memory
const char *fshader_str = NULL;
int fshader_size;
map_file(&fshader_str, &fshader_size, GLSL_FRAG_SHADER);
// get the shader into opengl
glShaderSource(gl_fragshader, 1, &fshader_str, NULL);
// compile the shader
glCompileShader(gl_fragshader);
if (debug_shader(gl_fragshader))
exit(EXIT_FAILURE);
// create the main program object, it is an amalgamation of all shaders
ren.gl_program = glCreateProgram();
// attach the shaders to the program (set which shaders are present)
glAttachShader(ren.gl_program, gl_vertshader);
glAttachShader(ren.gl_program, gl_fragshader);
// then link the program (basically the linking stage of the program)
glLinkProgram(ren.gl_program);
if (debug_program(ren.gl_program))
exit(EXIT_FAILURE);
// after linking the shaders can be detached and the source freed from
// memory since the program is ready to use
glDetachShader(ren.gl_program, gl_vertshader);
glDetachShader(ren.gl_program, gl_fragshader);
munmap((void *)vshader_str, vshader_size);
munmap((void *)fshader_str, fshader_size);
// now tell opengl to use the program
glUseProgram(ren.gl_program);
}
void ren_drawvertbuffer()
{
glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer);
glDrawArrays(GL_TRIANGLES, 0, VNUM(vertbuffer));
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
int w_height(SDL_Window *win)
{
int h;
SDL_GetWindowSize(win, NULL, &h);
return h;
}
int w_width(SDL_Window *win)
{
int w;
SDL_GetWindowSize(win, &w, NULL);
return w;
}
int main (void)
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
ren.w = SDL_CreateWindow("test",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
500, 500, SDL_WINDOW_OPENGL );
// create the OpenGL context
ren.gl = SDL_GL_CreateContext(ren.w);
if (ren.gl == NULL)
err(-1, "Failed to create OpenGL context: %s", SDL_GetError());
// initialize glew, this library gives us the declarations for most GL
// functions, in the future it would be cool to do it manually
GLenum glew_err = glewInit();
if (glew_err != GLEW_OK)
err(-1, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err));
ren_initvertbuffer();
ren_initshaders();
// event loop and drawing
SDL_Event ev = {0};
int running = 1;
do {
SDL_WaitEvent(&ev);
switch (ev.type) {
case SDL_QUIT: running = 0; break;
default: break;
}
glViewport(0, 0, w_width(ren.w), w_height(ren.w));
glClearColor(0.f, 0.f, 0.f, 0.f);
glClear(GL_COLOR_BUFFER_BIT);
ren_drawvertbuffer();
SDL_GL_SwapWindow(ren.w);
} while(running);
glDisableVertexAttribArray(vertindex);
glDisableVertexAttribArray(colindex);
SDL_GL_DeleteContext(ren.gl);
SDL_DestroyWindow(ren.w);
SDL_Quit();
return 0;
}

@ -1,14 +0,0 @@
#version 330
// use the input ("in") vertices from index zero
layout(location = 0) in vec4 position;
layout(location = 1) in vec4 color;
smooth out vec4 out_color;
void main()
{
// simply copy teh output position
gl_Position = position;
out_color = color;
}

@ -1,126 +0,0 @@
#ifndef _STACK_GENERIC_H
#define _STACK_GENERIC_H
#define STACK_STEP 8
#define STACK_SALT 0xbabb0cac
// FIXME: find a way to not re-hash the whole stack when removing one item
// incremental hash for every grow
#define STACK_HASH(p, s, h) \
{ \
unsigned char *v = (unsigned char *)(p); \
for (int x = (s); x; x--) { \
(h) += v[x-1]; \
(h) += (h) << 10; \
(h) ^= (h) >> 6; \
} \
(h) += (h) << 3; \
(h) ^= (h) >> 11; \
(h) += (h) << 15; \
}
// declare just the prototypes
#define STACK_PROTO(stackname, type) \
struct stackname { \
type *items; \
int size, idx, old_idx; \
unsigned int hash, old_hash; \
}; \
struct stackname stackname##_init(void); \
int stackname##_grow(struct stackname *stack, int step); \
int stackname##_push(struct stackname *stack, type *e); \
type stackname##_pop(struct stackname *stack); \
int stackname##_clear(struct stackname *stack); \
int stackname##_changed(struct stackname *stack); \
int stackname##_size_changed(struct stackname *stack); \
int stackname##_free(struct stackname *stack);
#define STACK_DEFINE(stackname, type) \
struct stackname; \
\
\
struct stackname stackname##_init(void) \
{ \
return (struct stackname){0, .hash = STACK_SALT}; \
} \
\
\
int stackname##_grow(struct stackname *stack, int step) \
{ \
if (!stack) \
return -1; \
stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \
if(!stack->items) \
return -1; \
memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \
stack->size += step; \
return 0; \
} \
\
\
int stackname##_push(struct stackname *stack, type *e) \
{ \
if (!stack || !e) \
return -1; \
if (stack->idx >= stack->size) \
if (stackname##_grow(stack, STACK_STEP)) \
return -1; \
stack->items[stack->idx++] = *e; \
STACK_HASH(e, sizeof(type), stack->hash); \
return 0; \
} \
\
\
type stackname##_pop(struct stackname *stack) \
{ \
if (!stack || stack->idx == 0 || stack->size == 0) \
return (type){0}; \
stack->hash = STACK_SALT; \
STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \
return stack->items[stack->idx--]; \
} \
\
\
int stackname##_clear(struct stackname *stack) \
{ \
if (!stack) \
return -1; \
stack->old_idx = stack->idx; \
stack->old_hash = stack->hash; \
stack->hash = STACK_SALT; \
stack->idx = 0; \
return 0; \
} \
\
\
int stackname##_changed(struct stackname *stack) \
{ \
if (!stack) \
return -1; \
return stack->hash != stack->old_hash; \
} \
\
\
int stackname##_size_changed(struct stackname *stack) \
{ \
if (!stack) \
return -1; \
return stack->size != stack->old_idx; \
} \
\
\
int stackname##_free(struct stackname *stack) \
{ \
if (stack) { \
stackname##_clear(stack); \
if (stack->items) \
free(stack->items); \
} \
return 0; \
} \
#endif

@ -6,6 +6,9 @@
#include <stdint.h>
// FIXME: change the api to just one HASH_DECL to HASH_PROTO and HASH_DEFINE
#define HASH_MAXSIZE 4096
// for fibonacci hashing, 2^{32,64}/<golden ratio>
#define HASH_RATIO32 ((uint64_t)2654435769u)

@ -0,0 +1,214 @@
//#!/usr/bin/tcc -run
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HASH_SALT (0xbabb0cac)
#define HASH_RATIO64 ((unsigned long int)11400714819322457583u)
struct ntree_node
{
unsigned int id;
const char *name;
int children_no, children_size;
struct ntree_node *children, *parent;
};
static char tmp_name_buffer[16] = {"node:"};
unsigned int hash_str_to_uint(const char *s)
{
unsigned int h = HASH_SALT;
const unsigned char *v = (const unsigned char *)(s);
for (int x = *s; x; x--) {
h += v[x-1];
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
h += h << 15;
return h;
}
unsigned int hash_u32(unsigned int c)
{
return (unsigned int)((((unsigned long int)c<<31)*HASH_RATIO64)>>32);
}
const char * generate_new_name(void)
{
static int count = 0;
unsigned int h = hash_u32(count++);
snprintf(tmp_name_buffer+sizeof("node:"), 16-sizeof("node:"), "%x", h);
return tmp_name_buffer;
}
int tree_prune(struct ntree_node *node)
{
if (node == NULL)
return 0;
if ((node->children_no == 0 && node->children != NULL) ||
(node->children_no != 0 && node->children == NULL)) {
printf("ERR: (%x %s) Inconsistent node children", node->id, node->name);
} else for (int i = 0; i < node->children_no; i++) {
tree_prune(&(node->children[i]));
}
if (node->children != NULL) {
free(node->children);
}
free((void *)node->name);
if (node->parent != NULL)
node->parent->children_no--;
*node = (struct ntree_node){0};
return 0;
}
struct ntree_node * tree_append(struct ntree_node *parent, const char *name)
{
if (name == NULL)
return NULL;
if (strlen(name) == 0)
name = generate_new_name();
// generate the children information
unsigned int id = hash_str_to_uint(name);
char *str = malloc(strlen(name)+1);
if (str == NULL)
return NULL;
strcpy(str, name);
// grow the parent buffer if necessary
if (parent->children_no >= parent->children_size) {
struct ntree_node *temp = NULL;
temp = realloc(parent->children, (parent->children_size+1)*sizeof(struct ntree_node));
if (temp == NULL) {
free(str);
return NULL;
}
parent->children = temp;
parent->children[parent->children_size] = (struct ntree_node){0};
parent->children_size++;
}
// find an open spot for the child
struct ntree_node *child = NULL;
for (int i = 0; i < parent->children_size; i++) {
if (parent->children[i].id == 0)
child = &(parent->children[i]);
}
if (child == NULL)
return NULL;
child->name = str;
child->id = id;
child->children = NULL;
child->children_no = 0;
child->parent = parent;
parent->children_no++;
//printf("append to %s, children: %d\n", parent->name, parent->children_no);
return child;
}
struct ntree_node * tree_find_id(struct ntree_node *root, unsigned int id)
{
if (id == 0 || root == NULL)
return NULL;
if (root->id == id)
return root;
else for (int i = 0; i < root->children_size; i++) {
if (root->children[i].id != 0 && tree_find_id(&(root->children[i]), id) != NULL)
return &(root->children[i]);
}
return NULL;
}
// TODO: add a foreach_child function
struct ntree_node * tree_find(struct ntree_node *root, const char *name)
{
if (name == NULL || strlen(name) == 0 || root == NULL)
return NULL;
unsigned int id = hash_str_to_uint(name);
return tree_find_id(root, id);
}
int tree_size(struct ntree_node *root)
{
if (root == NULL)
return 0;
int count = root->children_no;
if (count > 0) {
for (int i = 0; i < root->children_size; i++) {
if (root->children[i].id != 0)
count += tree_size(&(root->children[i]));
}
}
return count;
}
static int tree_print_ind(struct ntree_node *node, int dd)
{
for (int i = 0; i < dd; i++) printf(" ");
printf("[%s]\n", node->name);
for (int i = 0; i < node->children_size; i++) {
if (node->children[i].id == 0) continue;
tree_print_ind(&(node->children[i]), dd+1);
}
return node->children_no;
}
int tree_print(struct ntree_node *root)
{
if (root == NULL)
return 1;
tree_print_ind(root, 0);
return 0;
}
int main(void)
{
char *root_name = malloc(sizeof("root"));
strcpy(root_name, "root");
struct ntree_node root = {.name = root_name};
struct ntree_node *n;
n = tree_append(&root, "node 0:0");
tree_append(n, "node 0:0:0");
tree_append(&root, "node 0:1");
tree_append(&root, "node 0:2");
printf("Number of nodes %d\n", tree_size(&root));
tree_print(&root);
tree_prune(tree_find(&root, "node 0:0"));
printf("Number of nodes %d\n", tree_size(&root));
tree_prune(&root);
printf("Number of nodes %d\n", tree_size(&root));
return 0;
}

@ -0,0 +1,348 @@
#ifndef _VECTREE_H
#define _VECTREE_H
#ifdef VTREE_DTYPE
typedef struct {
int size, elements;
VTREE_DTYPE *vector;
int *refs;
} Vtree;
int vtree_init(Vtree *tree, unsigned int size);
int vtree_pack(Vtree *tree);
int vtree_resize(Vtree *tree, unsigned int newsize);
int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent);
int vtree_prune(Vtree *tree, int ref);
int vtree_subtree_size(Vtree *tree, int ref);
int vtree_children_it(Vtree *tree, int parent, int *cursor);
int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor);
int vtree_destroy(Vtree *tree);
#ifdef VTREE_IMPL
#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size)
#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0)
int vtree_init(Vtree *tree, unsigned int size)
{
if (tree == NULL) {
return -1;
}
tree->vector = malloc(sizeof(VTREE_DTYPE) * size);
if (tree->vector == NULL) {
return -1;
}
tree->refs = malloc(sizeof(int) * size);
if (tree->refs == NULL) {
free(tree->vector);
return -1;
}
// set all refs to -1, meaning invalid (free) element
for (unsigned int i = 0; i < size; i++) {
tree->refs[i] = -1;
}
// fill vector with zeroes
memset(tree->vector, 0, size * sizeof(VTREE_DTYPE));
tree->size = size;
tree->elements = 0;
return 0;
}
int vtree_destroy(Vtree *tree)
{
if (tree == NULL) {
return -1;
}
free(tree->vector);
free(tree->refs);
return 0;
}
int vtree_pack(Vtree *tree)
{
if (tree == NULL) {
return -1;
}
// TODO: add a PACKED flag to skip this
int free_spot = -1;
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == -1) {
free_spot = i;
continue;
}
// find a item that can be packed
if (free_spot >= 0 && tree->refs[i] >= 0) {
int old_ref = i;
// move the item
tree->vector[free_spot] = tree->vector[i];
tree->refs[free_spot] = tree->refs[i];
tree->vector[i] = (VTREE_DTYPE){0};
tree->refs[i] = -1;
// and move all references
for (int x = 0; x < tree->size; x++) {
if (tree->refs[x] == old_ref) {
tree->refs[x] = free_spot;
}
}
// mark the free spot as used
free_spot = -1;
}
}
return 0;
}
int vtree_resize(Vtree *tree, unsigned int newsize)
{
if (tree == NULL) {
return -1;
}
// return error when shrinking with too many elements
if ((int)newsize < tree->elements) {
return -1;
}
// pack the vector when shrinking to avoid data loss
if ((int)newsize < tree->size) {
//if (vtree_pack(tree) < 0) {
// return -1;
//}
// TODO: allow shrinking, since packing destroys all references
return -1;
}
VTREE_DTYPE *newvec = realloc(tree->vector, newsize * sizeof(VTREE_DTYPE));
if (newvec == NULL) {
return -1;
}
int *newrefs = realloc(tree->refs, newsize * sizeof(int));
if (newrefs == NULL) {
return -1;
}
tree->vector = newvec;
tree->refs = newrefs;
if ((int)newsize > tree->size) {
for (int i = tree->size; i < (int)newsize; i++) {
tree->vector[i] = (VTREE_DTYPE){0};
tree->refs[i] = -1;
}
}
tree->size = newsize;
return 0;
}
// add an element to the tree, return it's ref
int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent)
{
if (tree == NULL) {
return -1;
}
// invalid parent
if (!IS_VALID_REF(tree, parent)) {
return -1;
}
// no space left
if (tree->elements >= tree->size) {
return -1;
}
// check if the parent exists
// if there are no elements in the tree the first add will set the root
if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) {
return -1;
}
// get the first free spot
int free_spot = -1;
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == -1) {
free_spot = i;
break;
}
}
if (free_spot < 0) {
return -1;
}
// finally add the element
tree->vector[free_spot] = elem;
tree->refs[free_spot] = parent;
tree->elements++;
return free_spot;
}
// prune the tree starting from the ref
// returns the number of pruned elements
int vtree_prune(Vtree *tree, int ref)
{
if (tree == NULL) {
return -1;
}
if (!IS_VALID_REF(tree, ref)) {
return -1;
}
if (!REF_IS_PRESENT(tree, ref)) {
return 0;
}
tree->vector[ref] = (VTREE_DTYPE){0};
tree->refs[ref] = -1;
int count = 1;
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == ref) {
count += vtree_prune(tree, i);
}
}
return count;
}
// find the size of the subtree starting from ref
int vtree_subtree_size(Vtree *tree, int ref)
{
if (tree == NULL) {
return -1;
}
if (!IS_VALID_REF(tree, ref)) {
return -1;
}
if (!REF_IS_PRESENT(tree, ref)) {
return 0;
}
int count = 1;
for (int i = 0; i < tree->size; i++) {
// only root has the reference to itself
if (tree->refs[i] == ref && ref != i) {
count += vtree_subtree_size(tree, i);
}
}
return count;
}
// iterate through the first level children, use a cursor like strtok_r
int vtree_children_it(Vtree *tree, int parent, int *cursor)
{
if (tree == NULL || cursor == NULL) {
return -1;
}
// if the cursor is out of bounds then we are done for sure
if (!IS_VALID_REF(tree, *cursor)) {
return -1;
}
// same for the parent, if it's invalid it can't have children
if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) {
return -1;
}
// find the first child, update the cursor and return the ref
for (int i = *cursor; i < tree->size; i++) {
if (tree->refs[i] == parent) {
*cursor = i + 1;
return i;
}
}
// if no children are found return -1
*cursor = -1;
return -1;
}
/* iterates trough every leaf of the subtree in the following manner
* node [x], x: visit order
* [0]
* / | \
* / [2] [3]
* [1] |
* / \ [6]
* [4] [5]
*/
int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor)
{
if (tree == NULL || queue_p == NULL || cursor == NULL) {
return -1;
}
int *queue = *queue_p;
// TODO: this could also be done when adding or removing elements
// first call, create a ref array ordered like we desire
if (queue == NULL) {
*cursor = 0;
// create a queue of invalid refs, size is the worst case
queue = malloc(sizeof(int) * tree->size);
if (queue == NULL) {
return -1;
}
for (int i = 0; i < tree->size; i++) {
queue[i] = -1;
}
*queue_p = queue;
// iterate through the queue appending found children
int pos = 0, off = 0;
do {
//printf ("ref=%d\n", ref);
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == ref) {
queue[pos++] = i;
}
}
for (;ref == queue[off] && off < tree->size; off++);
ref = queue[off];
} while (IS_VALID_REF(tree, ref));
}
//PRINT_ARR(queue, tree->size);
//return -1;
// on successive calls just iterate through the queue until we find an
// invalid ref
int ret = queue[(*cursor)++];
if (!IS_VALID_REF(tree, ret)) {
free(queue);
}
return ret;
}
#endif // VTREE_IMPL
#endif // VTREE_DTYPE
#endif

@ -1,11 +0,0 @@
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
LDFLAGS = -lSDL2 -lm
test: main.c ../ugui.c ../ugui.h ../def_style.h ../input.c
gcc ${CFLAGS} -c ../ugui.c -o ugui.o
gcc ${CFLAGS} -c ../input.c -o input.o
gcc ${CFLAGS} -c main.c -o main.o
gcc ${LDFLAGS} main.o ugui.o input.o -o test
clean:
rm -f main.o ugui.o

@ -1,268 +0,0 @@
#define _POSIX_C_SOURCE 200809l
#include <stdio.h>
#include <SDL2/SDL.h>
#include "../ugui.h"
#define STB_RECT_PACK_IMPLEMENTATION
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTTF_IMPLEMENTATION
#include "stbttf.h"
SDL_Window *w;
SDL_Renderer *r;
ug_ctx_t *ctx;
STBTTF_Font *font;
void cleanup(void);
int main(void)
{
SDL_DisplayMode dm;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_EnableScreenSaver();
SDL_EventState(SDL_DROPFILE, SDL_ENABLE);
SDL_EventState(SDL_DROPTEXT, SDL_ENABLE);
SDL_GetDesktopDisplayMode(0, &dm);
#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* Available since 2.0.8 */
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#endif
#if SDL_VERSION_ATLEAST(2, 0, 5)
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
#endif
w = SDL_CreateWindow("test",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
dm.w*0.8, dm.h*0.8,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_OPENGL );
//SDL_Surface *s;
//s = SDL_GetWindowSurface(w);
r = SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED);
SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND);
ug_vec2_t size, dsize;
SDL_GetWindowSize(w, &size.w, &size.h);
SDL_GL_GetDrawableSize(w, &dsize.w, &dsize.h);
float scale = 1.0;
scale = ((float)(size.w+size.h)/2)/((float)(dsize.w+dsize.h)/2);
float dpi;
int idx;
idx = SDL_GetWindowDisplayIndex(w);
SDL_GetDisplayDPI(idx, &dpi, NULL, NULL);
ctx = ug_ctx_new();
ug_ctx_set_displayinfo(ctx, scale, dpi);
ug_ctx_set_drawableregion(ctx, dsize);
// open font
font = STBTTF_OpenFont(r, "monospace.ttf", ctx->style_px->title.font_size.size.i);
// atexit(cleanup);
SDL_Event event;
char button_map[] = {
[SDL_BUTTON_LEFT & 0xff] = UG_BTN_LEFT,
[SDL_BUTTON_MIDDLE & 0xff] = UG_BTN_MIDDLE,
[SDL_BUTTON_RIGHT & 0xff] = UG_BTN_RIGHT,
[SDL_BUTTON_X1 & 0xff] = UG_BTN_4,
[SDL_BUTTON_X2 & 0xff] = UG_BTN_5,
};
do {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_SHOWN:
(SDL_Log("Window %d shown", event.window.windowID));
break;
case SDL_WINDOWEVENT_HIDDEN:
(SDL_Log("Window %d hidden", event.window.windowID));
break;
case SDL_WINDOWEVENT_EXPOSED:
(SDL_Log("Window %d exposed", event.window.windowID));
break;
case SDL_WINDOWEVENT_MOVED:
(SDL_Log("Window %d moved to %d,%d",
event.window.windowID, event.window.data1,
event.window.data2));
break;
case SDL_WINDOWEVENT_RESIZED:
(SDL_Log("Window %d resized to %dx%d",
event.window.windowID, event.window.data1,
event.window.data2));
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
(SDL_Log("Window %d size changed to %dx%d",
event.window.windowID, event.window.data1,
event.window.data2));
size.w = event.window.data1;
size.h = event.window.data2;
// surface is invalidated every time the window
// is resized
//s = SDL_GetWindowSurface(w);
ug_ctx_set_drawableregion(ctx, size);
break;
case SDL_WINDOWEVENT_MINIMIZED:
(SDL_Log("Window %d minimized", event.window.windowID));
break;
case SDL_WINDOWEVENT_MAXIMIZED:
(SDL_Log("Window %d maximized", event.window.windowID));
break;
case SDL_WINDOWEVENT_RESTORED:
(SDL_Log("Window %d restored", event.window.windowID));
break;
case SDL_WINDOWEVENT_ENTER:
(SDL_Log("Mouse entered window %d",
event.window.windowID));
break;
case SDL_WINDOWEVENT_LEAVE:
(SDL_Log("Mouse left window %d", event.window.windowID));
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
(SDL_Log("Window %d gained keyboard focus",
event.window.windowID));
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
(SDL_Log("Window %d lost keyboard focus",
event.window.windowID));
break;
case SDL_WINDOWEVENT_CLOSE:
(SDL_Log("Window %d closed", event.window.windowID));
break;
#if SDL_VERSION_ATLEAST(2, 0, 5)
case SDL_WINDOWEVENT_TAKE_FOCUS:
(SDL_Log("Window %d is offered a focus", event.window.windowID));
break;
case SDL_WINDOWEVENT_HIT_TEST:
(SDL_Log("Window %d has a special hit test", event.window.windowID));
break;
#endif
default:
(SDL_Log("Window %d got unknown event %d",
event.window.windowID, event.window.event));
break;
}
break;
case SDL_QUIT:
(printf("Quitting\n"));
break;
case SDL_MOUSEMOTION:
ug_input_mousemove(ctx, event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
ug_input_mousemove(ctx, event.button.x, event.button.y);
ug_input_mousedown(ctx, button_map[event.button.button & 0xff]);
break;
case SDL_MOUSEBUTTONUP:
ug_input_mousemove(ctx, event.button.x, event.button.y);
ug_input_mouseup(ctx, button_map[event.button.button & 0xff]);
break;
default:
(printf("Unknown event: %d\n", event.type));
break;
}
ug_frame_begin(ctx);
//if (ctx->frame < 5000) {
// ug_container_menu_bar(ctx, "Menu fichissimo", (ug_size_t)SIZE_PX(24));
//} else if (ctx->frame == 5000) {
// ug_container_remove(ctx, "Menu fichissimo");
//}
ug_container_floating(ctx, "stupid name", (ug_div_t){.x=SIZE_PX(0), .y=SIZE_PX(0), .w=SIZE_PX(100), .h=SIZE_MM(75.0)});
ug_element_button(ctx, "float", "X", (ug_div_t){SQUARE(SIZE_MM(5))});
//ug_container_floating(ctx, "floating windoooooooow", (ug_div_t){.x=SIZE_PX(100), .y=SIZE_PX(0), .w=SIZE_PX(100), .h=SIZE_MM(75.0)});
//ug_container_sidebar(ctx, "Right Sidebar", (ug_size_t)SIZE_PX(300), UG_SIDE_RIGHT);
//ug_container_sidebar(ctx, "Left Sidebar", (ug_size_t)SIZE_PX(200), UG_SIDE_LEFT);
//ug_container_sidebar(ctx, "Bottom Sidebar", (ug_size_t)SIZE_MM(10), UG_SIDE_BOTTOM);
ug_container_sidebar(ctx, "Top Sidebar", (ug_size_t)SIZE_MM(40), UG_SIDE_TOP);
// ug_container_popup(ctx, "Annoying popup", (ug_div_t){.x=SIZE_MM(150), .y=SIZE_MM(150), .w=SIZE_PX(100), .h=SIZE_MM(75.0)});
//ug_container_body(ctx, "Main Body");
//if (ug_container_body(ctx, "Other Body"))
// printf("No space!\n");
ug_layout_row(ctx);
ug_layout_column(ctx);
ug_element_button(ctx, "button 1", "hey", (ug_div_t){SQUARE(SIZE_MM(10))});
//ug_element_button(ctx, "button 2", "lol", (ug_div_t){SQUARE(SIZE_MM(10))});
//ug_layout_next_row(ctx);
//ug_element_button(ctx, "button 3", "L", (ug_div_t){SQUARE(SIZE_MM(10))});
//ug_element_button(ctx, "button 4", "69", (ug_div_t){SQUARE(SIZE_MM(10))});
ug_layout_next_column(ctx);
ug_element_button(ctx, "button 5", "lmao", (ug_div_t){SQUARE(SIZE_MM(10))});
//ug_element_textbtn(ctx, "text button 1", "foo", (ug_div_t){.w = 0, .h = SIZE_PX(30)});
//ug_element_button(ctx, "button 6", "", (ug_div_t){SQUARE(SIZE_MM(10)),.x=SIZE_PX(-10)});
ug_container_body(ctx, "fill body");
ug_frame_end(ctx);
// fill background
// solid purple makes it easy to identify, same color as hl missing texture
SDL_SetRenderDrawColor(r, 0xff, 0, 0xdc, 0xff);
SDL_RenderClear(r);
for (ug_cmd_t *cmd = NULL; (cmd = ug_cmd_next(ctx));) {
switch (cmd->type) {
case UG_CMD_RECT:
{
ug_color_t col = cmd->rect.color;
SDL_Rect sr = {
.x = cmd->rect.x,
.y = cmd->rect.y,
.w = cmd->rect.w,
.h = cmd->rect.h,
};
SDL_SetRenderDrawColor(r, col.r, col.g, col.b, col.a);
SDL_RenderFillRect(r, &sr);
}
break;
case UG_CMD_TEXT:
SDL_SetRenderDrawColor(r, cmd->text.color.r, cmd->text.color.g, cmd->text.color.b, cmd->text.color.a);
STBTTF_RenderText(r, font, cmd->text.x, cmd->text.y, cmd->text.str);
break;
default: break;
}
}
SDL_RenderPresent(r);
} while (event.type != SDL_QUIT);
cleanup();
return 0;
}
void cleanup(void)
{
ug_ctx_free(ctx);
STBTTF_CloseFont(font);
SDL_DestroyRenderer(r);
SDL_DestroyWindow(w);
SDL_Quit();
}

Binary file not shown.

@ -1,623 +0,0 @@
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
// Sean Barrett 2014
//
// Useful for e.g. packing rectangular textures into an atlas.
// Does not do rotation.
//
// Before #including,
//
// #define STB_RECT_PACK_IMPLEMENTATION
//
// in the file that you want to have the implementation.
//
// Not necessarily the awesomest packing method, but better than
// the totally naive one in stb_truetype (which is primarily what
// this is meant to replace).
//
// Has only had a few tests run, may have issues.
//
// More docs to come.
//
// No memory allocations; uses qsort() and assert() from stdlib.
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
//
// This library currently uses the Skyline Bottom-Left algorithm.
//
// Please note: better rectangle packers are welcome! Please
// implement them to the same API, but with a different init
// function.
//
// Credits
//
// Library
// Sean Barrett
// Minor features
// Martins Mozeiko
// github:IntellectualKitty
//
// Bugfixes / warning fixes
// Jeremy Jaussaud
// Fabian Giesen
//
// Version history:
//
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
// 0.99 (2019-02-07) warning fixes
// 0.11 (2017-03-03) return packing success/fail result
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
// 0.09 (2016-08-27) fix compiler warnings
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
// 0.05: added STBRP_ASSERT to allow replacing assert
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
// 0.01: initial release
//
// LICENSE
//
// See end of file for license information.
//////////////////////////////////////////////////////////////////////////////
//
// INCLUDE SECTION
//
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#define STB_INCLUDE_STB_RECT_PACK_H
#define STB_RECT_PACK_VERSION 1
#ifdef STBRP_STATIC
#define STBRP_DEF static
#else
#define STBRP_DEF extern
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef struct stbrp_context stbrp_context;
typedef struct stbrp_node stbrp_node;
typedef struct stbrp_rect stbrp_rect;
typedef int stbrp_coord;
#define STBRP__MAXVAL 0x7fffffff
// Mostly for internal use, but this is the maximum supported coordinate value.
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
// Assign packed locations to rectangles. The rectangles are of type
// 'stbrp_rect' defined below, stored in the array 'rects', and there
// are 'num_rects' many of them.
//
// Rectangles which are successfully packed have the 'was_packed' flag
// set to a non-zero value and 'x' and 'y' store the minimum location
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
// if you imagine y increasing downwards). Rectangles which do not fit
// have the 'was_packed' flag set to 0.
//
// You should not try to access the 'rects' array from another thread
// while this function is running, as the function temporarily reorders
// the array while it executes.
//
// To pack into another rectangle, you need to call stbrp_init_target
// again. To continue packing into the same rectangle, you can call
// this function again. Calling this multiple times with multiple rect
// arrays will probably produce worse packing results than calling it
// a single time with the full rectangle array, but the option is
// available.
//
// The function returns 1 if all of the rectangles were successfully
// packed and 0 otherwise.
struct stbrp_rect
{
// reserved for your use:
int id;
// input:
stbrp_coord w, h;
// output:
stbrp_coord x, y;
int was_packed; // non-zero if valid packing
}; // 16 bytes, nominally
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
// Initialize a rectangle packer to:
// pack a rectangle that is 'width' by 'height' in dimensions
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
//
// You must call this function every time you start packing into a new target.
//
// There is no "shutdown" function. The 'nodes' memory must stay valid for
// the following stbrp_pack_rects() call (or calls), but can be freed after
// the call (or calls) finish.
//
// Note: to guarantee best results, either:
// 1. make sure 'num_nodes' >= '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
#ifndef STBRP_SORT
#include <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#define STBRP_ASSERT assert
#endif
#ifdef _MSC_VER
#define STBRP__NOTUSED(v) (void)(v)
#define STBRP__CDECL __cdecl
#else
#define STBRP__NOTUSED(v) (void)sizeof(v)
#define STBRP__CDECL
#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;
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;
context->extra[1].y = (1<<30);
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__NOTUSED(c);
STBRP_ASSERT(first->x <= x0);
#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);
// if it can't possibly fit, bail immediately
if (width > c->width || height > c->height) {
fr.prev_link = NULL;
fr.x = fr.y = 0;
return fr;
}
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);
{
int count=0;
cur = context->active_head;
while (cur) {
cur = cur->next;
++count;
}
cur = context->free_head;
while (cur) {
cur = cur->next;
++count;
}
STBRP_ASSERT(count == context->num_nodes+2);
}
#endif
return res;
}
static int STBRP__CDECL 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 STBRP__CDECL 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);
}
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
{
int i, all_rects_packed = 1;
// we use the 'was_packed' field internally to allow sorting/unsorting
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = i;
}
// sort according to heuristic
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
for (i=0; i < num_rects; ++i) {
if (rects[i].w == 0 || rects[i].h == 0) {
rects[i].x = rects[i].y = 0; // empty rect needs no space
} else {
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
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
// set was_packed flags and all_rects_packed status
for (i=0; i < num_rects; ++i) {
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
if (!rects[i].was_packed)
all_rects_packed = 0;
}
// return the all_rects_packed status
return all_rects_packed;
}
#endif
/*
------------------------------------------------------------------------------
This software is available under 2 licenses -- choose whichever you prefer.
------------------------------------------------------------------------------
ALTERNATIVE A - MIT License
Copyright (c) 2017 Sean Barrett
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
------------------------------------------------------------------------------
ALTERNATIVE B - Public Domain (www.unlicense.org)
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load Diff

@ -1,212 +0,0 @@
#ifndef __STBTTF_H__
#define __STBTTF_H__
#include <SDL2/SDL.h>
#include "stb_rect_pack.h"
#include "stb_truetype.h"
/* STBTTF: A quick and dirty SDL2 text renderer based on stb_truetype and stdb_rect_pack.
* Benoit Favre 2019
*
* This header-only addon to the stb_truetype library allows to draw text with SDL2 from
* TTF fonts with a similar API to SDL_TTF without the bloat.
* The renderer is however limited by the integral positioning of SDL blit functions.
* It also does not parse utf8 text and only prints ASCII characters.
*
* This code is public domain.
*/
typedef struct {
stbtt_fontinfo* info;
stbtt_packedchar* chars;
SDL_Texture* atlas;
int texture_size;
float size;
float scale;
int ascent;
int baseline;
} STBTTF_Font;
/* Release the memory and textures associated with a font */
void STBTTF_CloseFont(STBTTF_Font* font);
/* Open a TTF font given a SDL abstract IO handler, for a given renderer and a given font size.
* Returns NULL on failure. The font must be deallocated with STBTTF_CloseFont when not used anymore.
* This function creates a texture atlas with prerendered ASCII characters (32-128).
*/
STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size);
/* Open a TTF font given a filename, for a given renderer and a given font size.
* Convinience function which calls STBTTF_OpenFontRW.
*/
STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size);
/* Draw some text using the renderer draw color at location (x, y).
* Characters are copied from the texture atlas using the renderer SDL_RenderCopy function.
* Since that function only supports integral coordinates, the result is not great.
* Only ASCII characters (32 <= c < 128) are supported. Anything outside this range is ignored.
*/
void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text);
/* Return the length in pixels of a text.
* You can get the height of a line by using font->baseline.
*/
float STBTTF_MeasureText(STBTTF_Font* font, const char *text);
#ifdef STBTTF_IMPLEMENTATION
void STBTTF_CloseFont(STBTTF_Font* font) {
if(font->atlas) SDL_DestroyTexture(font->atlas);
if(font->info) free(font->info);
if(font->chars) free(font->chars);
free(font);
}
STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size) {
Sint64 file_size = SDL_RWsize(rw);
unsigned char* buffer = malloc(file_size);
if(SDL_RWread(rw, buffer, file_size, 1) != 1) return NULL;
SDL_RWclose(rw);
STBTTF_Font* font = calloc(sizeof(STBTTF_Font), 1);
font->info = malloc(sizeof(stbtt_fontinfo));
font->chars = malloc(sizeof(stbtt_packedchar) * 96);
if(stbtt_InitFont(font->info, buffer, 0) == 0) {
free(buffer);
STBTTF_CloseFont(font);
return NULL;
}
// fill bitmap atlas with packed characters
unsigned char* bitmap = NULL;
font->texture_size = 32;
while(1) {
bitmap = malloc(font->texture_size * font->texture_size);
stbtt_pack_context pack_context;
stbtt_PackBegin(&pack_context, bitmap, font->texture_size, font->texture_size, 0, 1, 0);
stbtt_PackSetOversampling(&pack_context, 1, 1);
if(!stbtt_PackFontRange(&pack_context, buffer, 0, size, 32, 95, font->chars)) {
// too small
free(bitmap);
stbtt_PackEnd(&pack_context);
font->texture_size *= 2;
} else {
stbtt_PackEnd(&pack_context);
break;
}
}
// convert bitmap to texture
font->atlas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, font->texture_size, font->texture_size);
SDL_SetTextureBlendMode(font->atlas, SDL_BLENDMODE_BLEND);
Uint32* pixels = malloc(font->texture_size * font->texture_size * sizeof(Uint32));
static SDL_PixelFormat* format = NULL;
if(format == NULL) format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32);
for(int i = 0; i < font->texture_size * font->texture_size; i++) {
pixels[i] = SDL_MapRGBA(format, 0xff, 0xff, 0xff, bitmap[i]);
}
SDL_UpdateTexture(font->atlas, NULL, pixels, font->texture_size * sizeof(Uint32));
free(pixels);
free(bitmap);
// setup additional info
font->scale = stbtt_ScaleForPixelHeight(font->info, size);
stbtt_GetFontVMetrics(font->info, &font->ascent, 0, 0);
font->baseline = (int) (font->ascent * font->scale);
free(buffer);
return font;
}
STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size) {
SDL_RWops *rw = SDL_RWFromFile(filename, "rb");
if(rw == NULL) return NULL;
return STBTTF_OpenFontRW(renderer, rw, size);
}
void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text) {
Uint8 r, g, b, a;
SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
SDL_SetTextureColorMod(font->atlas, r, g, b);
SDL_SetTextureAlphaMod(font->atlas, a);
for(int i = 0; text[i]; i++) {
if (text[i] >= 32 && text[i] < 128) {
//if(i > 0) x += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale;
stbtt_packedchar* info = &font->chars[text[i] - 32];
SDL_Rect src_rect = {info->x0, info->y0, info->x1 - info->x0, info->y1 - info->y0};
SDL_Rect dst_rect = {x + info->xoff, y + info->yoff, info->x1 - info->x0, info->y1 - info->y0};
SDL_RenderCopy(renderer, font->atlas, &src_rect, &dst_rect);
x += info->xadvance;
}
}
}
float STBTTF_MeasureText(STBTTF_Font* font, const char *text) {
float width = 0;
for(int i = 0; text[i]; i++) {
if (text[i] >= 32 && text[i] < 128) {
//if(i > 0) width += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale;
stbtt_packedchar* info = &font->chars[text[i] - 32];
width += info->xadvance;
}
}
return width;
}
/*******************
* Example program *
*******************
#include <stdio.h>
#define STB_RECT_PACK_IMPLEMENTATION
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTTF_IMPLEMENTATION
#include "stbttf.h"
int main(int argc, char** argv) {
if(argc != 2) {
fprintf(stderr, "usage: %s <font>\n", argv[0]);
exit(1);
}
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("stbttf", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_RenderSetLogicalSize(renderer, 640, 480);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
STBTTF_Font* font = STBTTF_OpenFont(renderer, argv[1], 32);
while(1) {
SDL_Event event;
while(SDL_PollEvent(&event)) {
if(event.type == SDL_QUIT) exit(0);
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// set color and render some text
SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255);
STBTTF_RenderText(renderer, font, 50, 50, "This is a test");
// render the atlas to check its content
//SDL_Rect dest = {0, 0, font->texturesize, font->texturesize};
//SDL_RenderCopy(renderer, font->atlas, &dest, &dest);
SDL_RenderPresent(renderer);
SDL_Delay(1000 / 60);
}
STBTTF_CloseFont(font);
SDL_Quit();
}
*/
#endif
#endif

@ -1,4 +0,0 @@
*.png
test
obj/**
objlist

@ -1,18 +0,0 @@
CC = gcc
# instrumentation flags
INFLAGS = -fsanitize=address,builtin,undefined
LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL ${INFLAGS}
CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 -Wno-unused-function -fno-omit-frame-pointer ${INFLAGS}
.PHONY: clean all
all: test
font.o: font.c font.h stb_truetype.h stb_image_write.h util.h \
generic_cache.h generic_hash.h
main.o: main.c ren.h util.h
ren.o: ren.c util.h font.h ren.h generic_stack.h
util.o: util.c util.h
test: font.o main.o ren.o util.o
${CC} ${LDFLAGS} -o test font.o main.o ren.o util.o
clean:
rm -f test font.o main.o ren.o util.o

@ -1,8 +0,0 @@
#version 330 core
in vec4 col;
void main()
{
gl_FragColor = col;
}

@ -1,19 +0,0 @@
#version 330 core
uniform ivec2 viewsize;
layout(location = 0) in vec2 position;
layout(location = 2) in vec4 color;
out vec4 col;
void main()
{
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
gl_Position = pos;
col = color;
}

@ -1,195 +0,0 @@
#define _POSIX_C_SOURCE 200809l
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <grapheme.h>
#include <assert.h>
#include "font.h"
#include "stb_truetype.h"
#include "stb_image_write.h"
#include "util.h"
#include "generic_cache.h"
static inline unsigned int hash(unsigned int code)
{
// identity map the ascii range
return code < 128 ? code : hash_u32(code);
}
CACHE_DECL(cache, struct font_glyph, hash, hash_cmp_u32)
#define UTF8(c) (c&0x80)
#define BDEPTH 1
#define BORDER 4
// FIXME: as of now only monospaced fonts work correctly since no kerning information
// is stored
struct priv {
stbtt_fontinfo stb;
float scale;
int baseline;
unsigned char *bitmap;
struct cache c;
};
#define PRIV(x) ((struct priv *)x->priv)
struct font_atlas * font_init(void)
{
struct font_atlas *p = emalloc(sizeof(struct font_atlas));
memset(p, 0, sizeof(struct font_atlas));
p->priv = emalloc(sizeof(struct priv));
memset(p->priv, 0, sizeof(struct priv));
PRIV(p)->c = cache_init();
return p;
}
// loads a font into memory, storing all the ASCII characters in the atlas, each font
// atlas structure holds glyphs of a specific size in pixels
// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall)
int font_load(struct font_atlas *atlas, const char *path, int size)
{
if (!atlas || !path)
return -1;
int err;
dump_file(path, &(atlas->file), &(atlas->file_size));
err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0);
if (err == 0) return -1;
int ascent, descent, linegap, baseline;
int x0,y0,x1,y1;
float scale;
stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap);
stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1);
scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size);
baseline = scale * -y0;
atlas->glyph_max_w = (scale*x1) - (scale*x0);
atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0);
atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
PRIV(atlas)->baseline = atlas->glyph_max_h - baseline;
PRIV(atlas)->scale = scale;
PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h);
// FIXME: make this a square atlas
atlas->width = atlas->glyph_max_w*CACHE_SIZE/4;
atlas->height = atlas->glyph_max_h*4;
atlas->size = size;
// preallocate all ascii characters
for (char c = ' '; c <= '~'; c++) {
if (!font_get_glyph_texture(atlas, c, NULL))
return -1;
}
return 0;
}
int font_free(struct font_atlas *atlas)
{
efree(atlas->atlas);
efree(atlas->file);
efree(PRIV(atlas)->bitmap);
cache_free(&PRIV(atlas)->c);
efree(atlas->priv);
efree(atlas);
return 0;
}
// TODO: time and take the median of the time it takes to generate the cache and
// the time it takes to draw the glyph
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated)
{
int _u = 0;
if (!updated) updated = &_u;
const struct font_glyph *r;
if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) {
*updated = 0;
return r;
}
*updated = 1;
// generate the sdf and put it into the cache
// TODO: generate the whole block at once
int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code);
int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base;
base = atlas->glyph_max_h - PRIV(atlas)->baseline;
stbtt_GetGlyphBitmapBoxSubpixel(
&PRIV(atlas)->stb,
idx,
PRIV(atlas)->scale,
PRIV(atlas)->scale,
0,0,
&x0,&y0,
&x1, &y1);
gw = x1 - x0;
gh = y1 - y0;
stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l);
adv *= PRIV(atlas)->scale;
off_x = PRIV(atlas)->scale*l;
off_y = atlas->glyph_max_h+y0;
stbtt_MakeGlyphBitmapSubpixel(
&PRIV(atlas)->stb,
PRIV(atlas)->bitmap,
atlas->glyph_max_w,
atlas->glyph_max_h,
atlas->glyph_max_w,
PRIV(atlas)->scale,
PRIV(atlas)->scale,
0, 0,
idx);
// TODO: bounds check usign atlas height
// TODO: clear spot area in the atlas before writing on it
unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c);
unsigned int ty = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h;
unsigned int tx = (atlas->glyph_max_w * spot) % atlas->width;
unsigned int w = atlas->width;
unsigned char *a = (void *)atlas->atlas;
//printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y);
for (int y = 0; y < gh; y++) {
for (int x = 0; x < gw; x++) {
int c, r;
r = (ty+y)*w;
c = tx+x;
a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x];
}
}
struct font_glyph g = {
.codepoint = code,
.u = tx,
.v = ty,
.w = gw,
.h = gh,
.x = off_x,
.y = off_y-base,
.a = adv,
};
return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot);
}
void font_dump(const struct font_atlas *atlas, const char *path)
{
stbi_write_png(
path,
atlas->width,
atlas->height,
BDEPTH,
atlas->atlas,
BDEPTH*atlas->width);
}

@ -1,50 +0,0 @@
#ifndef _FONT_H
#define _FONT_H
#include <stdint.h>
/* width and height of a glyph contain the kering advance
* (u,v)
* +-------------*---+ -
* | ^ | | ^
* | |oy | | |
* | v | | |
* | .ii. | | |
* | @@@@@@. |<->| |
* | V@Mio@@o |adv| |
* | :i. V@V | | |
* | :oM@@M | | |
* | :@@@MM@M | | |
* | @@o o@M | | |
* |<->:@@. M@M | | |
* |ox @@@o@@@@ | | |
* | :M@@V:@@.| | v
* +-------------*---+ -
* |<------------->|
* w
*/
// TODO: the advance isn't unique for every pair of characters
struct font_glyph {
uint32_t codepoint;
uint32_t u, v;
uint16_t w, h, a, x, y;
};
struct font_atlas {
unsigned int width, height;
unsigned char *atlas;
unsigned int glyph_max_w, glyph_max_h;
int size;
int file_size;
char *file;
void *priv;
};
struct font_atlas * font_init(void);
int font_load(struct font_atlas *atlas, const char *path, int size);
int font_free(struct font_atlas *atlas);
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated);
void font_dump(const struct font_atlas *atlas, const char *path);
#endif

@ -1,18 +0,0 @@
#version 330 core
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
uniform ivec2 viewsize;
uniform ivec2 texturesize;
// texture uv coordinate in texture space
in vec2 uv;
uniform sampler2DRect ts;
const vec3 textcolor = vec3(1.0, 1.0, 1.0);
void main()
{
//gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f);
gl_FragColor = vec4(textcolor, texture(ts, uv));
}

@ -1,26 +0,0 @@
#version 330 core
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
uniform ivec2 viewsize;
uniform ivec2 texturesize;
// both position and and uv are in pixels, they where converted to floats when
// passed to the shader
layout(location = 0) in vec2 position;
layout(location = 1) in vec2 txcoord;
out vec2 uv;
void main()
{
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f);
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
gl_Position = pos;
// since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to
// be normalized
uv = vec2(txcoord.x, txcoord.y);
}

@ -1,126 +0,0 @@
#ifndef CACHE_GENERIC_H
#define CACHE_GENERIC_H
#include "generic_hash.h"
#define CACHE_SIZE 512
#define CACHE_NCYCLES (CACHE_SIZE*2/3)
#define CACHE_BSIZE (((CACHE_SIZE+0x3f)&(~0x3f))>>6)
#define CACHE_BRESET(b) for (int i = 0; i < CACHE_BSIZE; b[i++] = 0);
#define CACHE_BSET(b, x) b[(x)>>6] |= (uint64_t)1<<((x)&63)
#define CACHE_BTEST(b, x) (b[(x)>>6]&((uint64_t)1<<((x)&63)))
// FIXME: this cache implementation is not really generic since it expects an unsigned
// as the code and not a generic type
#define CACHE_CYCLE(c) \
{ \
if (++(c->cycles) > CACHE_NCYCLES) { \
for (int i = 0; i < CACHE_BSIZE; i++) { \
c->present[i] &= c->used[i]; \
c->used[i] = 0; \
} \
c->cycles = 0; \
} \
}
#define CACHE_DECL(name, type, hashfn, cmpfn) \
HASH_DECL(name##table, uint32_t, uint32_t, hashfn, cmpfn) \
struct name { \
struct name##table_ref *table; \
type *array; \
uint64_t *present, *used; \
int cycles; \
}; \
\
\
/* FIXME: check for allocation errors */ \
struct name name##_init(void) \
{ \
struct name##table_ref *t = name##table_create(CACHE_SIZE); \
type *a = malloc(sizeof(type)*CACHE_SIZE); \
uint64_t *p = malloc(sizeof(uint64_t)*CACHE_BSIZE); \
uint64_t *u = malloc(sizeof(uint64_t)*CACHE_BSIZE); \
CACHE_BRESET(p); \
CACHE_BRESET(u); \
return (struct name){.table=t, .array=a, .present=p, .used=u, 0}; \
} \
\
\
void name##_free(struct name *cache) \
{ \
if (cache) { \
name##table_destroy(cache->table); \
free(cache->array); \
free(cache->present); \
free(cache->used); \
} \
} \
\
\
const type * name##_search(struct name *cache, uint32_t code) \
{ \
if (!cache) return NULL; \
struct name##table_entry *r; \
r = name##table_search(cache->table, code); \
/* MISS */ \
if (!r || !cmpfn(code, r->code)) \
return NULL; \
/* MISS, the data is not valid (not present) */ \
if (!CACHE_BTEST(cache->present, r->data)) \
return NULL; \
/* HIT, set as recently used */ \
CACHE_BSET(cache->used, r->data); \
return (&cache->array[r->data]); \
} \
\
\
/* Look for a free spot in the present bitmap and return its index */ \
/* If there is no free space left then just return the first position */ \
int name##_get_free_spot(struct name *cache) \
{ \
if (!cache) return -1; \
int x = 0; \
for (int b = 0; b < CACHE_BSIZE; b++) { \
if (cache->present[b] == 0) x = 64; \
else x = __builtin_clzll(cache->present[b]); \
x = 64-x; \
if (!CACHE_BTEST(cache->present, x+64*b)) \
return x+64*b; \
} \
return 0; \
} \
\
\
const type * name##_insert_at(struct name *cache, const type *g, uint32_t code, int x)\
{ \
if (!cache) return NULL; \
type *spot = NULL; \
/* check if the spot is valid */ \
if (x < 0) return NULL; \
/* Set used and present */ \
CACHE_BSET(cache->present, x); \
CACHE_BSET(cache->used, x); \
CACHE_CYCLE(cache) \
spot = &(cache->array[x]); \
*spot = *g; \
struct name##table_entry e = { .code = code, .data = x}; \
if (!name##table_insert(cache->table, &e)) \
return NULL; \
return spot; \
} \
\
\
const type * name##_insert(struct name *cache, const type *g, uint32_t code, int *x)\
{ \
int y; \
if (!x) x = &y; \
*x = name##_get_free_spot(cache); \
return name##_insert_at(cache, g, code, *x); \
} \
#endif

@ -1,46 +0,0 @@
#!/bin/sh
genobj() {
#echo "$1" | sed -e 's/^/obj\//' -e 's/\.c/\.o/'
echo "$1" | sed -e 's/\.c/\.o/'
}
genrule() {
#gcc -MM "$1" | sed 's/^/obj\//'
gcc -MM "$1"
}
rm -f objlist
cat > Makefile << EOF
CC = gcc
# instrumentation flags
INFLAGS = -fsanitize=address,builtin,undefined
LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL \${INFLAGS}
CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 \
-Wno-unused-function -fno-omit-frame-pointer \${INFLAGS}
.PHONY: clean all
all: test
EOF
for f in *.c; do
genrule $f >> Makefile
genobj $f >> objlist
done
mainrule='test: '
linkcmd=' ${CC} ${LDFLAGS} -o test '
cleanrule='clean:
rm -f test '
while IFS="" read -r line; do
mainrule="$mainrule $line"
linkcmd="$linkcmd $line"
cleanrule="$cleanrule $line"
done < objlist
echo "$mainrule" >> Makefile
echo "$linkcmd" >> Makefile
echo "$cleanrule" >> Makefile

@ -1,96 +0,0 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_video.h>
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#include "ren.h"
#include "util.h"
//const char *str1 = "Ciao Mamma!\nprova: òçà°ù§|¬³¼$£ì\t";
const char *str1 = "ciao\tmamma";
const char *str2 = "The quick brown fox jumps over the lazy dog\n"
"чащах юга жил бы цитрус? Да, но фальшивый экземпляр!\n"
"Pijamalı hasta, yağız şoföre çabucak güvendi\n"
"Pchnąć w tę łódź jeża lub ośm skrzyń fig\n"
"イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン\n"
"Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa";
SDL_Window *win;
#define red 0xff0000ff
#define blue 0xffff0000
void draw(void)
{
static unsigned int frame = 0;
printf("frame: %d\n", frame++);
ren_clear();
//ren_render_box(10, 10, 400, 50, blue);
//if (ren_render_text(str1, 10, 10, 400, 50, 20))
// printf("text: %s\n", ren_strerror());
int w, h;
ren_get_text_box(str2, &w, &h, 20);
//printf("box for: %s -> (%d, %d)\n", str2, w, h);
ren_render_box(0, 0, w, h, blue);
ren_render_text(str2, 0, 0, w, h, 20);
// fixme: this causes a bug
const char *s = "ciao mamma";
ren_get_text_box(s, &w, &h, 12);
s = "stuff that was not in font size 12 -> чащаx";
ren_render_box(0, 200, w, h, red);
if (ren_render_text(s, 0, 200, 0xffff, 0xffff, 12))
printf("BUG\n");
SDL_GL_SwapWindow(win);
}
int main(void)
{
setlocale(LC_ALL, "en_US.UTF-8");
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0");
win = SDL_CreateWindow(
"test render",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
500,
500,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
if (ren_init(win)) {
printf("renderer init error: %s\n", ren_strerror());
return 1;
}
SDL_Event e;
while(1) {
SDL_WaitEvent(&e);
if (e.type == SDL_QUIT)
break;
if (e.type == SDL_WINDOWEVENT) {
switch (e.window.event) {
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
ren_update_viewport(e.window.data1, e.window.data2);
draw();
break;
case SDL_WINDOWEVENT_EXPOSED:
draw();
break;
default: break;
}
}
}
ren_free();
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}

@ -1,826 +0,0 @@
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <SDL2/SDL_opengl.h>
#include <SDL2/SDL_video.h>
#include <ctype.h>
#include <grapheme.h>
#include <stdio.h>
#include "util.h"
#include "font.h"
#include "ren.h"
#define GLERR(x) \
{ \
int a = glGetError(); \
if (a != GL_NO_ERROR) \
printf("(%s:%d %s:%s) glError: 0x%x %s\n", \
__FILE__, __LINE__, __func__, x, a, glerr[a&0xff]); \
}
#define GL(f) f; GLERR(#f)
#define REN_RET(a,b) {ren_errno = b; return a;}
enum REN_ERR {
REN_SUCCESS = 0,
REN_ERRNO,
REN_INVAL,
REN_VERTEX,
REN_FRAGMENT,
REN_PROGRAM,
REN_COMPILE,
REN_LINK,
REN_TEXTURE,
REN_CONTEXT,
REN_GLEW,
REN_FONT,
REN_BUFFER,
REN_UNIFORM,
};
// TODO: make a macro for enum-associated string arrays
const char * ren_err_msg[] = {
[REN_SUCCESS] = "Success",
[REN_ERRNO] = "Look at errno",
[REN_INVAL] = "Invalid or NULL arguments",
[REN_VERTEX] = "Failed to create opengl vertex shader",
[REN_FRAGMENT] = "Failed to create opengl fragment shader",
[REN_PROGRAM] = "Failed to create opengl program",
[REN_COMPILE] = "Failed to compile shaders",
[REN_LINK] = "Failed to link shaders",
[REN_TEXTURE] = "Failed to create texture",
[REN_CONTEXT] = "Failed to create SDL OpenGL context",
[REN_GLEW] = "GLEW Error",
[REN_FONT] = "Font Error",
[REN_BUFFER] = "Failed to create opengl buffer",
[REN_UNIFORM] = "Failed to get uniform location",
};
#define ELEM(x) [x&0xff] = #x,
const char *glerr[] = {
ELEM(GL_INVALID_ENUM)
ELEM(GL_INVALID_VALUE)
ELEM(GL_INVALID_OPERATION)
ELEM(GL_OUT_OF_MEMORY)
};
#undef ELEM
// different stacks
#include "generic_stack.h"
STACK_DECL(vtstack, struct v_text)
STACK_DECL(vcstack, struct v_col)
struct ren_font {
struct font_atlas *font;
GLuint texture;
};
struct {
SDL_GLContext *gl;
struct ren_font *fonts;
int fonts_no;
GLuint font_prog;
GLuint box_prog;
GLuint font_buffer;
GLuint box_buffer;
GLint viewsize_loc;
GLint box_viewsize_loc;
GLint texturesize_loc;
struct vtstack font_stack;
struct vcstack box_stack;
int width, height;
int s_x, s_y, s_w, s_h;
int tabsize;
} ren = {0};
static int ren_errno;
// print shader compilation errors
static int shader_compile_error(GLuint shader, const char *path)
{
GLint status;
GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status))
if (status != GL_FALSE)
return 0;
GLint log_length;
GL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length))
GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar));
GL(glGetShaderInfoLog(shader, log_length, NULL, log_str))
const char *shader_type_str = NULL;
GLint shader_type;
GL(glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type))
switch(shader_type) {
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
}
fprintf(stderr, "Compile failure in %s shader %s:\n%s\n", shader_type_str, path, log_str);
efree(log_str);
return -1;
}
// print shader link errors
static int shader_link_error(GLuint prog)
{
GLint status;
GL(glGetProgramiv(prog, GL_LINK_STATUS, &status))
if (status != GL_FALSE)
return 0;
GLint log_length;
GL(glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length))
GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar));
GL(glGetProgramInfoLog(prog, log_length, NULL, log_str))
fprintf(stderr, "Linker failure: %s\n", log_str);
efree(log_str);
return -1;
}
static GLuint shader_compile(const char *path, const char *shader, GLuint type)
{
GLuint s;
// initialize the vertex shader and get the corresponding id
s = GL(glCreateShader(type))
if (!s) REN_RET(0, REN_VERTEX)
// get the shader into opengl
GL(glShaderSource(s, 1, &shader, NULL))
GL(glCompileShader(s))
if (shader_compile_error(s, path))
REN_RET(0, REN_COMPILE)
return s;
}
const char * ren_strerror(void)
{
return ren_err_msg[ren_errno % (sizeof(ren_err_msg)/sizeof(char *))];
}
// compile a vertex shader (vs_path) and a fragment shader (fs_path) into an opengl
// program and return it's index
static GLuint ren_compile_program(const char *vs_path, const char *fs_path)
{
GLuint gl_vertshader, gl_fragshader, prog;
if (!vs_path || !fs_path)
REN_RET(0, REN_INVAL)
char *str = NULL;
dump_file(vs_path, &str, NULL);
if (!str) REN_RET(0, REN_ERRNO)
gl_vertshader = shader_compile(vs_path, str, GL_VERTEX_SHADER);
efree(str);
if (!gl_vertshader)
return 0;
dump_file(fs_path, &str, NULL);
if (!str) REN_RET(0, REN_ERRNO)
gl_fragshader = shader_compile(fs_path, str, GL_FRAGMENT_SHADER);
efree(str);
if (!gl_fragshader)
return 0;
// create the main program object, it is an amalgamation of all shaders
prog = GL(glCreateProgram())
if (!prog) REN_RET(0, REN_PROGRAM)
// attach the shaders to the program (set which shaders are present)
GL(glAttachShader(prog, gl_vertshader))
GL(glAttachShader(prog, gl_fragshader))
// then link the program (basically the linking stage of the program)
GL(glLinkProgram(prog))
if (shader_link_error(prog))
REN_RET(0, REN_LINK)
// after linking the shaders can be detached and the source freed from
// memory since the program is ready to use
GL(glDetachShader(prog, gl_vertshader))
GL(glDetachShader(prog, gl_fragshader))
return prog;
}
static void set_texture_parameters(GLuint type, GLuint wrap_s, GLuint wrap_t, GLuint upscale, GLuint downscale)
{
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s))
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t))
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, upscale))
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, downscale))
}
static GLuint ren_texturergb_2d(const char *buf, int w, int h, int upscale, int downscale)
{
GLuint t;
if (!buf || w <= 0 || h <= 0)
REN_RET(0, REN_INVAL)
GL(glGenTextures(1, &t))
if (!t) REN_RET(0, REN_TEXTURE)
GL(glBindTexture(GL_TEXTURE_2D, t))
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, buf))
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
return t;
}
static GLuint ren_texturergba_2d(const char *buf, int w, int h, int upscale, int downscale)
{
GLuint t;
if (!buf || w <= 0 || h <= 0)
REN_RET(0, REN_INVAL)
GL(glGenTextures(1, &t))
if (!t) REN_RET(0, REN_TEXTURE)
GL(glBindTexture(GL_TEXTURE_2D, t))
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf))
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
return t;
}
static GLuint ren_texturer_2d(const char *buf, int w, int h, int upscale, int downscale)
{
GLuint t;
if (!buf || w <= 0 || h <= 0)
REN_RET(0, REN_INVAL)
GL(glGenTextures(1, &t))
if (!t) REN_RET(0, REN_TEXTURE)
GL(glBindTexture(GL_TEXTURE_2D, t))
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf))
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
return t;
}
static GLuint ren_texturer_rect(const char *buf, int w, int h, int upscale, int downscale)
{
GLuint t;
if (!buf || w <= 0 || h <= 0)
REN_RET(0, REN_INVAL)
GL(glGenTextures(1, &t))
if (!t) REN_RET(0, REN_TEXTURE)
GL(glBindTexture(GL_TEXTURE_RECTANGLE, t))
GL(glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf))
// a limitation of recatngle textures is that the wrapping mode is limited
// to either clamp-to-edge or clamp-to-border
set_texture_parameters(GL_TEXTURE_RECTANGLE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, upscale, downscale);
return t;
}
// FIXME: update only the newly generated character instead of the whole texture
static int update_font_texture(int idx)
{
GL(glUseProgram(ren.font_prog))
GL(glTexSubImage2D(
GL_TEXTURE_RECTANGLE,
0, 0, 0,
ren.fonts[idx].font->width,
ren.fonts[idx].font->height,
GL_RED,
GL_UNSIGNED_BYTE,
ren.fonts[idx].font->atlas))
//font_dump(ren.fonts[idx].font, "./atlas.png");
GL(glUseProgram(0));
return 0;
}
// loads a font and returns it's index in the fonts array
static int ren_load_font(int size, const char *path)
{
int idx = ren.fonts_no;
struct font_atlas *f;
ren.fonts = erealloc(ren.fonts, sizeof(struct ren_font)*(idx+1));
ren.fonts[idx].font = font_init();
f = ren.fonts[idx].font;
if (!f)
REN_RET(-1, REN_FONT)
if (!path)
path = DEFAULT_FONT;
if (font_load(f, path, size))
REN_RET(-1, REN_FONT)
//font_dump(f, "./atlas.png");
// load font texture (atlas)
ren.fonts[idx].texture = ren_texturer_rect(
(const char *)f->atlas,
f->width,
f->height,
GL_LINEAR, GL_LINEAR);
if (!ren.fonts[idx].texture)
return -1;
ren.fonts_no = idx+1;
return idx;
}
// returns the index to the ren.fonts array to the font with the correct size
// return -1 on errror
static int ren_get_font(int size)
{
for (int i = 0; i < ren.fonts_no; i++) {
if (ren.fonts[i].font->size == size)
return i;
}
// TODO: add a way to change font family
return ren_load_font(size, NULL);
}
int ren_init(SDL_Window *w)
{
// Initialize OpenGL
if (!w)
REN_RET(-1, REN_INVAL)
// using version 3 does not allow to use glVertexAttribPointer() without
// vertex buffer objects, so use compatibility mode
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
ren.gl = SDL_GL_CreateContext(w);
if (!ren.gl) {
printf("SDL: %s\n", SDL_GetError());
REN_RET(-1, REN_CONTEXT)
}
GL(glEnable(GL_BLEND))
GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA))
GL(glDisable(GL_CULL_FACE))
GL(glDisable(GL_DEPTH_TEST))
GL(glEnable(GL_SCISSOR_TEST))
GL(glEnable(GL_TEXTURE_2D))
GL(glEnable(GL_TEXTURE_RECTANGLE))
GLenum glew_err = glewInit();
if (glew_err != GLEW_OK)
REN_RET(glew_err, REN_GLEW);
// Create stacks
ren.font_stack = vtstack_init();
ren.box_stack = vcstack_init();
// generate the font buffer object
GL(glGenBuffers(1, &ren.font_buffer))
if (!ren.font_buffer) REN_RET(-1, REN_BUFFER)
GL(glGenBuffers(1, &ren.box_buffer))
if (!ren.box_buffer) REN_RET(-1, REN_BUFFER)
// Compile font shaders
ren.font_prog = ren_compile_program(FONT_VERSHADER, FONT_FRAGSHADER);
if (!ren.font_prog) return -1;
// create the uniforms, if the returned value is -1 then the uniform may have
// been optimized away, in any case do not return, just give a warning
ren.viewsize_loc = GL(glGetUniformLocation(ren.font_prog, "viewsize"))
if (ren.viewsize_loc == -1)
printf("uniform %s was optimized away\n", "viewsize");
ren.texturesize_loc = GL(glGetUniformLocation(ren.font_prog, "texturesize"))
if (ren.texturesize_loc == -1)
printf("uniform %s was optimized away\n", "texturesize");
// Compile box shaders
ren.box_prog = ren_compile_program(BOX_VERSHADER, BOX_FRAGSHADER);
if (!ren.box_prog) return -1;
ren.box_viewsize_loc = GL(glGetUniformLocation(ren.box_prog, "viewsize"))
if (ren.box_viewsize_loc == -1)
printf("uniform %s was optimized away\n", "viewsize");
// Finishing touches
ren.tabsize = REN_TABSIZE;
int width, height;
SDL_GetWindowSize(w, &width, &height);
ren_update_viewport(width, height);
GL(glClearColor(0.3f, 0.3f, 0.3f, 0.f))
GL(glClear(GL_COLOR_BUFFER_BIT))
return 0;
}
int ren_clear(void)
{
GL(glScissor(0, 0, ren.width, ren.height))
GL(glClear(GL_COLOR_BUFFER_BIT));
return 0;
}
// idx refers to the fonts array index, this means that when drawing the font stack
// only one font size at the time can be used
static int ren_draw_font_stack(int idx)
{
struct font_atlas *font = ren.fonts[idx].font;
GLuint font_texture = ren.fonts[idx].texture;
GL(glUseProgram(ren.font_prog))
GL(glBindTexture(GL_TEXTURE_RECTANGLE, font_texture))
GL(glViewport(0, 0, ren.width, ren.height))
// this has caused me some trouble, convert from image coordiates to viewport
GL(glScissor(ren.s_x, ren.height-ren.s_y-ren.s_h, ren.s_w, ren.s_h))
GL(glUniform2i(ren.viewsize_loc, ren.width, ren.height))
GL(glUniform2i(ren.texturesize_loc, font->width, font->height))
GL(glBindBuffer(GL_ARRAY_BUFFER, ren.font_buffer))
if (vtstack_changed(&ren.font_stack)) {
if (vtstack_size_changed(&ren.font_stack)) {
GL(glBufferData(
GL_ARRAY_BUFFER,
ren.font_stack.idx*sizeof(struct v_text),
ren.font_stack.items,
GL_DYNAMIC_DRAW))
} else {
GL(glBufferSubData(
GL_ARRAY_BUFFER,
0,
ren.font_stack.idx*sizeof(struct v_text),
ren.font_stack.items))
}
}
// when passing ints to glVertexAttribPointer they are automatically
// converted to floats
GL(glVertexAttribPointer(
REN_VERTEX_IDX,
2,
GL_INT,
GL_FALSE,
sizeof(struct v_text),
0))
GL(glVertexAttribPointer(
REN_UV_IDX,
2,
GL_INT,
GL_FALSE,
sizeof(struct v_text),
(void*)sizeof(vec2_i)))
GL(glEnableVertexAttribArray(REN_VERTEX_IDX))
GL(glEnableVertexAttribArray(REN_UV_IDX))
GL(glDrawArrays(GL_TRIANGLES, 0, ren.font_stack.idx))
GL(glDisableVertexAttribArray(REN_VERTEX_IDX))
GL(glDisableVertexAttribArray(REN_UV_IDX))
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
GL(glBindTexture(GL_TEXTURE_RECTANGLE, 0))
GL(glUseProgram(0))
vtstack_clear(&ren.font_stack);
return 0;
}
static int ren_draw_box_stack(void)
{
GL(glUseProgram(ren.box_prog))
GL(glViewport(0, 0, ren.width, ren.height))
GL(glScissor(0, 0, ren.width, ren.height))
GL(glUniform2i(ren.box_viewsize_loc, ren.width, ren.height))
GL(glBindBuffer(GL_ARRAY_BUFFER, ren.box_buffer))
if(vcstack_changed(&ren.box_stack)) {
if (vcstack_size_changed(&ren.box_stack)) {
GL(glBufferData(
GL_ARRAY_BUFFER,
ren.box_stack.idx*sizeof(struct v_col),
ren.box_stack.items,
GL_DYNAMIC_DRAW))
} else {
GL(glBufferSubData(
GL_ARRAY_BUFFER,
0,
ren.box_stack.idx*sizeof(struct v_col),
ren.box_stack.items))
}
}
// when passing ints to glVertexAttribPointer they are automatically
// converted to floats
GL(glVertexAttribPointer(
REN_VERTEX_IDX,
2,
GL_INT,
GL_FALSE,
sizeof(struct v_col),
0))
// the color gets normalized
GL(glVertexAttribPointer(
REN_COLOR_IDX,
4,
GL_INT,
GL_TRUE,
sizeof(struct v_col),
(void*)sizeof(vec2_i)))
GL(glEnableVertexAttribArray(REN_VERTEX_IDX))
GL(glEnableVertexAttribArray(REN_COLOR_IDX))
GL(glDrawArrays(GL_TRIANGLES, 0, ren.box_stack.idx))
GL(glDisableVertexAttribArray(REN_VERTEX_IDX))
GL(glDisableVertexAttribArray(REN_COLOR_IDX))
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
GL(glUseProgram(0))
vcstack_clear(&ren.box_stack);
return 0;
}
int ren_update_viewport(int w, int h)
{
ren.width = w;
ren.height = h;
return 0;
}
int ren_set_scissor(int x, int y, int w, int h)
{
ren.s_x = x;
ren.s_y = y;
ren.s_w = w;
ren.s_h = h;
return 0;
}
static int ren_push_glyph(const struct font_glyph *g, int gx, int gy)
{
/* x4,y4 x3,y3
* o-------------+
* |(x,y) /|
* | / |
* | 2 / |
* | / |
* | / |
* | / 1 |
* |/ |
* +-------------+
* x1,y1 x2,y2 */
struct v_text v;
struct font_glyph c;
c = *g;
//printf("g: u=%d v=%d w=%d h=%d a=%d x=%d y=%d\n", c.u, c.v, c.w, c.h, c.a, c.x, c.y);
//printf("v: x=%d y=%d u=%d v=%d\n", v.pos.x, v.pos.y, v.uv.u, v.uv.v);
// x1,y1
v = (struct v_text){
.pos = { .x = gx+c.x, .y = gy+c.y+c.h },
.uv = { .u = c.u, .v = c.v+c.h },
};
vtstack_push(&ren.font_stack, &v);
// x2,y2
v = (struct v_text){
.pos = { .x = gx+c.x+c.w, .y = gy+c.y+c.h },
.uv = { .u = c.u+c.w, .v = c.v+c.h },
};
vtstack_push(&ren.font_stack, &v);
// x3,y3
v = (struct v_text){
.pos = { .x = gx+c.x+c.w, .y = gy+c.y },
.uv = { .u = c.u+c.w, .v = c.v },
};
vtstack_push(&ren.font_stack, &v);
// x1,y1
v = (struct v_text){
.pos = { .x = gx+c.x, .y = gy+c.y+c.h },
.uv = { .u = c.u, .v = c.v+c.h },
};
vtstack_push(&ren.font_stack, &v);
// x3,y3
v = (struct v_text){
.pos = { .x = gx+c.x+c.w, .y = gy+c.y },
.uv = { .u = c.u+c.w, .v = c.v },
};
vtstack_push(&ren.font_stack, &v);
// x4,y4
v = (struct v_text){
.pos = { .x = gx+c.x, .y = gy+c.y },
.uv = { .u = c.u, .v = c.v },
};
vtstack_push(&ren.font_stack, &v);
return 0;
}
static const struct font_glyph * get_glyph(unsigned int code, int idx)
{
const struct font_glyph *g;
int updated;
g = font_get_glyph_texture(ren.fonts[idx].font, code, &updated);
if (!g)
REN_RET(NULL, REN_FONT);
if (updated) {
if (update_font_texture(idx))
REN_RET(NULL, REN_TEXTURE);
}
return g;
}
// TODO: reduce repeating patterns in ren_get_text_box() and ren_render_text()
int ren_get_text_box(const char *str, int *rw, int *rh, int size)
{
int w = 0, h = 0, x = 0, y = 0;
const struct font_glyph *g;
size_t off, ret;
uint32_t cp;
int idx = ren_get_font(size);
if (idx < 0)
return -1;
h = y = ren.fonts[idx].font->glyph_max_h;
for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
if (iscntrl(cp)) goto skip_get;
if (!(g = get_glyph(cp, idx)))
return -1;
x += g->x + g->a;
// FIXME: generalize this thing
skip_get:
switch (cp) {
case '\t': {
const struct font_glyph *sp = get_glyph(' ', idx);
if (!sp) return -1;
x += (sp->x + sp->a)*ren.tabsize;
}
break;
case '\r':
x = 0;
break;
case '\n':
// TODO: encode and/or store line height
y += ren.fonts[idx].font->glyph_max_h;
x = 0;
break;
default: break;
}
if (x > w) w = x;
if (y > h) h = y;
}
if (rw) *rw = w;
if (rh) *rh = h;
return 0;
}
int ren_render_text(const char *str, int x, int y, int w, int h, int size)
{
const struct font_glyph *g;
size_t ret, off;
uint32_t cp;
int gx = x, gy = y;
int idx = ren_get_font(size);
if (idx < 0)
return -1;
for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
// skip special characters that render a box (not present in font)
if (iscntrl(cp)) goto skip_render;
if (!(g = get_glyph(cp, idx)))
return -1;
// only push the glyph if it is inside the bounding box
if (gx <= x+w && gy <= y+h)
ren_push_glyph(g, gx, gy);
// TODO: possible kerning needs to be applied here
// TODO: handle other unicode control characters such as the
// right-to-left isolate (\u2067)
gx += g->x + g->a;
skip_render:
switch (cp) {
case '\t': {
const struct font_glyph *sp = get_glyph(' ', idx);
if (!sp) return -1;
gx += (sp->x + sp->a)*ren.tabsize;
}
break;
case '\r':
gx = x;
break;
case '\n':
gy += ren.fonts[idx].font->glyph_max_h;
gx = x;
break;
default: break;
}
}
ren_set_scissor(x, y, w, h);
ren_draw_font_stack(idx);
return 0;
}
// re-normalize color from 0-255 to 0-0x7fffffff, technically i'm dividing by 256 here
#define RENORM(x) (((unsigned long long)(x)*0x7fffffff)>>8)
#define R(x) (x&0xff)
#define G(x) ((x>>8)&0xff)
#define B(x) ((x>>16)&0xff)
#define A(x) ((x>>24)&0xff)
static int ren_push_box(int x, int y, int w, int h, unsigned int color)
{
/* x4,y4 x3,y3
* o-------------+
* |(x,y) /|
* | / |
* | 2 / |
* | / |
* | / |
* | / 1 |
* |/ |
* +-------------+
* x1,y1 x2,y2 */
struct v_col v;
vec4_i c = {
.r = RENORM(R(color)),
.g = RENORM(G(color)),
.b = RENORM(B(color)),
.a = RENORM(A(color)),
};
// x1, y1
v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c };
vcstack_push(&ren.box_stack, &v);
// x2, y2
v = (struct v_col){ .pos = { .x = x+w, .y = y+h }, .col = c };
vcstack_push(&ren.box_stack, &v);
// x3, y3
v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c };
vcstack_push(&ren.box_stack, &v);
// x1, y1
v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c };
vcstack_push(&ren.box_stack, &v);
// x3, y3
v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c };
vcstack_push(&ren.box_stack, &v);
// x4, y4
v = (struct v_col){ .pos = { .x = x, .y = y }, .col = c };
vcstack_push(&ren.box_stack, &v);
return 0;
}
int ren_render_box(int x, int y, int w, int h, unsigned int color)
{
ren_push_box(x, y, w, h, color);
ren_draw_box_stack();
return 0;
}
int ren_free(void)
{
GL(glUseProgram(0))
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
GL(glDeleteProgram(ren.box_prog));
GL(glDeleteProgram(ren.font_prog));
GL(glDeleteBuffers(1, &ren.font_buffer))
for (int i = 0; i < ren.fonts_no; i++) {
GL(glDeleteTextures(1, &ren.fonts[i].texture))
font_free(ren.fonts[i].font);
}
SDL_GL_DeleteContext(ren.gl);
vtstack_free(&ren.font_stack);
vcstack_free(&ren.box_stack);
return 0;
}

@ -1,54 +0,0 @@
#ifndef _RENDERER_H
#define _RENDERER_H
#include <SDL2/SDL.h>
#define DEFAULT_FONT "/usr/share/fonts/TTF/FiraCode-Regular.ttf"
#define FONT_VERSHADER "./font_vertshader.glsl"
#define FONT_FRAGSHADER "./font_fragshader.glsl"
#define BOX_VERSHADER "./box_vertshader.glsl"
#define BOX_FRAGSHADER "./box_fragshader.glsl"
#define REN_VERTEX_IDX 0
#define REN_UV_IDX 1
#define REN_COLOR_IDX 2
#define REN_TABSIZE 8
typedef struct {
union { int x, u; };
union { int y, v; };
} vec2_i;
typedef struct {
union { int x, r; };
union { int y, g; };
union { int z, b; };
union { int w, a; };
} vec4_i;
// textured vertex
struct v_text {
vec2_i pos;
vec2_i uv;
};
// colored vertex
struct v_col {
vec2_i pos;
vec4_i col;
};
int ren_init(SDL_Window *sdl_window);
int ren_free(void);
const char * ren_strerror(void);
int ren_update_viewport(int w, int h);
int ren_set_scissor(int x, int y, int w, int h);
int ren_get_text_box(const char *str, int *rw, int *rh, int size);
int ren_render_text(const char *str, int x, int y, int w, int h, int size);
int ren_render_box(int x, int y, int w, int h, unsigned int color);
int ren_clear(void);
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,126 +0,0 @@
#define _POSIX_C_SOURCE 200809l
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include <time.h>
#include "util.h"
const char *bit_rep[16] = {
[ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011",
[ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111",
[ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011",
[12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111",
};
void * emalloc(unsigned long int size)
{
void *r = malloc(size);
if (!r)
err(EXIT_FAILURE, "malloc() of size %ld", size);
return r;
}
void * ecalloc(unsigned long int nmemb, unsigned long int size)
{
void *r = calloc(nmemb, size);
if (!r)
err(EXIT_FAILURE, "calloc() of size %ld, nmemb %ld", size, nmemb);
return r;
}
void * erealloc(void *ptr, unsigned long int size)
{
void *r = realloc(ptr, size);
if (!r)
err(EXIT_FAILURE, "ralloc() of 0x%lx, to size %ld", (unsigned long)ptr, size);
return r;
}
void efree(void *ptr)
{
if (ptr)
free(ptr);
}
void map_file(const unsigned char **str, int *size, const char *path)
{
if (!path) {
errno = EINVAL;
err(EXIT_FAILURE, "NULL filename");
}
FILE *fp = fopen(path, "r");
if (!fp)
err(EXIT_FAILURE, "Cannot open file %s", path);
*size = lseek(fileno(fp), 0, SEEK_END);
if (*size == (off_t)-1)
err(EXIT_FAILURE, "lseek() failed");
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
if (*str == (void*)-1)
err(EXIT_FAILURE, "mmap() failed");
if (fclose(fp))
err(EXIT_FAILURE, "Error closing file");
}
void dump_file(const char *path, char **buf, int *buf_len)
{
if (!path) {
errno = EINVAL;
err(EXIT_FAILURE, "NULL filename");
}
if (!buf) {
errno = EINVAL;
err(EXIT_FAILURE, "No buffer specified");
}
int m = 0;
if (!buf_len)
buf_len = &m;
FILE *fp = fopen(path, "r");
if (!fp)
err(EXIT_FAILURE, "Cannot open file %s", path);
*buf_len = lseek(fileno(fp), 0, SEEK_END);
rewind(fp);
if (*buf_len == (off_t)-1)
err(EXIT_FAILURE, "lseek() failed");
*buf = emalloc(*buf_len+1);
memset(*buf, 0, *buf_len+1);
int ret = fread(*buf, 1, *buf_len, fp);
if (ret != *buf_len)
err(EXIT_FAILURE, "fread() returned short %s", ferror(fp) ? "stream error" : feof(fp) ? "EOF reached" : "unknown error");
if (fclose(fp))
err(EXIT_FAILURE, "Error closing file");
}
void print_byte(unsigned char byte)
{
printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]);
}
static struct timespec clock_start, clock_stop;
void stopwatch_start(void)
{
clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_start);
}
double stopwatch_get(void)
{
clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_stop);
return (clock_stop.tv_sec-clock_start.tv_sec)+(double)(clock_stop.tv_nsec-clock_start.tv_nsec)/(double)1000000000L;
}

@ -1,34 +0,0 @@
#ifndef _UTIL_H
#define _UTIL_H
#include <stdio.h>
void * emalloc(unsigned long int size);
void * ecalloc(unsigned long int nmemb, unsigned long int size);
void * erealloc(void *ptr, unsigned long int size);
void efree(void *ptr);
void map_file(const unsigned char **str, int *size, const char *path);
void dump_file(const char *path, char **buf, int *buf_len);
void print_byte(unsigned char byte);
void stopwatch_start(void);
double stopwatch_get(void);
#define TIME_SEC(f) \
{ \
stopwatch_start(); \
f; \
printf("\"%s\" took %f seconds", #f, stopwatch_get()); \
}
#define TIME_MS(f) \
{ \
stopwatch_start(); \
f; \
printf("\"%s\" took %f ms", #f, stopwatch_get()*1000.0f); \
}
#endif

@ -0,0 +1,69 @@
#define _POSIX_C_SOURCE 200809l
#include <time.h>
#include "timer.h"
const clockid_t CLOCK_ID = CLOCK_PROCESS_CPUTIME_ID;
struct _timer {
struct timespec start, stop;
struct timespec times[TIMER_MAX_PARTIAL];
} timer = {0};
int timer_start(void) { return clock_gettime(CLOCK_ID, &timer.start); }
int timer_reset(void)
{
timer = (struct _timer) {0};
return 0;
}
int timer_stop(void) { return clock_gettime(CLOCK_ID, &timer.stop); }
// partial clocks also update the stop time
int timer_partial(int idx)
{
if (idx > TIMER_MAX_PARTIAL || idx < 0) {
return -1;
}
clock_gettime(CLOCK_ID, &timer.stop);
return clock_gettime(CLOCK_ID, &(timer.times[idx]));
}
size_t timer_get_us(int idx)
{
if (idx > TIMER_MAX_PARTIAL) {
return -1;
}
struct timespec ts = {0};
if (idx < 0) {
ts = timer.stop;
} else {
ts = timer.times[idx];
}
ts.tv_sec -= timer.start.tv_sec;
ts.tv_nsec -= timer.start.tv_nsec;
// FIXME: check overflow
return (ts.tv_nsec / 1000) + (ts.tv_sec * 1000000);
}
double timer_get_sec(int idx)
{
if (idx > TIMER_MAX_PARTIAL) {
return -1;
}
struct timespec ts = {0};
if (idx < 0) {
ts = timer.stop;
} else {
ts = timer.times[idx];
}
ts.tv_sec -= timer.start.tv_sec;
ts.tv_nsec -= timer.start.tv_nsec;
return (double)ts.tv_sec + ((double)ts.tv_nsec / 1e9);
}

@ -0,0 +1,16 @@
#ifndef UG_TIMER_H_
#define UG_TIMER_H_
#include <stdlib.h>
#define TIMER_MAX_PARTIAL 10
int timer_start(void);
int timer_stop(void);
int timer_reset(void);
int timer_partial(int idx);
size_t timer_get_us(int idx);
double timer_get_sec(int idx);
#endif

2054
ugui.c

File diff suppressed because it is too large Load Diff

352
ugui.h

@ -1,285 +1,105 @@
#ifndef _UG_HEADER
#define _UG_HEADER
#ifndef _UGUI_H
#define _UGUI_H
#define UG_STACK(T) struct { T *items; int idx, size, sorted; }
#define BIT(n) (1 << n)
#define RGBA_FORMAT(x) { .a=x&0xff, .b=(x>>8)&0xff, .g=(x>>16)&0xff, .r=(x>>24)&0xff }
#define RGB_FORMAT(x) { .a=0xff, .b=x&0xff, .g=(x>>8)&0xff, .r=(x>>16)&0xff }
#define SIZE_PX(x) { .size.i=x, .unit=UG_UNIT_PX }
#define SIZE_MM(x) { .size.f=x, .unit=UG_UNIT_MM }
#define SIZE_PT(x) { .size.f=x, .unit=UG_UNIT_PT }
#define SQUARE(x) .w = x, .h = x
#include <stdint.h>
// basic types
typedef unsigned int ug_id_t;
typedef struct { union {int x, w;}; union {int y, h;}; } ug_vec2_t;
typedef struct { unsigned char a, b, g, r; } ug_color_t;
typedef struct { int x, y, w, h; } ug_rect_t;
typedef struct { union {int i; float f;} size; int unit; } ug_size_t;
// div has information about the phisical dimension
typedef struct { ug_size_t x, y, w, h;} ug_div_t;
typedef enum {
UG_UNIT_PX = 0,
UG_UNIT_MM,
UG_UNIT_PT,
} ug_unit_t;
// element type
typedef struct {
ug_id_t id;
unsigned short int type;
unsigned short int flags;
ug_rect_t rect, rca;
const char *name;
union {
struct {
const char *txt;
} btn;
};
} ug_element_t;
enum {
UG_ELEM_BUTTON, // button
UG_ELEM_TXTBTN, // textual button, a button but without frame
UG_ELEM_CHECK, // checkbox
UG_ELEM_RADIO, // radio button
UG_ELEM_TOGGLE, // toggle button
UG_ELEM_LABEL, // simple text
UG_ELEM_UPDOWN, // text with two buttons up and down
UG_ELEM_TEXTINPUT, // text input box
UG_ELEM_TEXTBOX, // text surrounded by a box
UG_ELEM_IMG, // image, icon
UG_ELEM_SPACE, // takes up space
};
int32_t x, y, w, h;
} UgRect;
enum {
ELEM_CLIPPED = BIT(0),
};
// container type, a container is an entity that contains layouts, a container has
// a haight a width and their maximum values, in essence a container is a rectangular
// area that can be resized and sits somewhere on the drawable region
// the z index of a container is determined by it's position on the stack
typedef struct {
ug_id_t id;
const char *name;
ug_rect_t rect;
// absolute position rect
ug_rect_t rca;
unsigned int flags;
// layouting and elements
// total space used by elements, x and y are the starting coordinates
// for elements
ug_rect_t space;
// origin for in-row and in-column elements
ug_vec2_t c_orig, r_orig;
UG_STACK(ug_element_t) elem_stack;
ug_id_t selected_elem, hover_elem;
} ug_container_t;
// the container flags
enum {
UG_CNT_FLOATING = BIT(0), // is on top of everything else
UG_CNT_RESIZE_RIGHT = BIT(1), // can be resized from the right border
UG_CNT_RESIZE_BOTTOM = BIT(2), // can be resized from the bottom border
UG_CNT_RESIZE_LEFT = BIT(3), // can be resized from the left border
UG_CNT_RESIZE_TOP = BIT(4), // can be resized from the top border
UG_CNT_SCROLL_X = BIT(5), // can have horizontal scrolling
UG_CNT_SCROLL_Y = BIT(6), // can have vertical scrolling
UG_CNT_MOVABLE = BIT(7), // can be moved around
// container state
CNT_STATE_NONE = BIT(30),
CNT_STATE_MOVING = BIT(29),
CNT_STATE_RESIZE_T = BIT(28),
CNT_STATE_RESIZE_B = BIT(27),
CNT_STATE_RESIZE_L = BIT(26),
CNT_STATE_RESIZE_R = BIT(25),
CNT_STATE_RESIZE_D = BIT(24),
CNT_STATE_DELETE = BIT(23), // The container is marked for removal
// layouting
CNT_LAYOUT_COLUMN = BIT(22),
};
int32_t x, y;
} UgPoint;
// style, defines default height, width, color, margins, borders, etc
// all dimensions should be taken as a reference when doing the layout since
// ultimately it's the layout that decides them. For example when deciding how to
// allocate space one can say that the default size of a region that allocates a
// slider has the style's default dimensions for a slider
typedef struct {
struct { ug_color_t bg, fg; } color;
ug_size_t margin;
struct {
ug_color_t color;
ug_size_t size;
} border;
struct {
struct { ug_color_t bg, fg; } color;
ug_size_t height, font_size;
} title;
struct {
struct { ug_color_t act, bg, fg, sel, br; } color;
ug_size_t font_size, border;
} btn;
} ug_style_t;
uint8_t r, g, b, a;
} UgColor;
// render commands
struct ug_cmd_rect { int x, y, w, h; ug_color_t color; };
struct ug_cmd_text { int x, y, size; ug_color_t color; const char *str; };
typedef struct {
unsigned int type;
union {
struct ug_cmd_rect rect;
struct ug_cmd_text text;
};
} ug_cmd_t;
typedef uint64_t UgId;
typedef enum {
UG_CMD_NULL = 0,
UG_CMD_RECT,
UG_CMD_TEXT,
} ug_cmd_type_t;
// window side
enum {
UG_SIDE_TOP = 0,
UG_SIDE_BOTTOM,
UG_SIDE_LEFT,
UG_SIDE_RIGHT,
ETYPE_NONE = 0,
ETYPE_DIV,
ETYPE_BUTTON,
} UgElemType;
enum UgElemFlags {
ELEM_UPDATED = 1 << 0,
ELEM_HASFOCUS = 1 << 1,
};
// mouse buttons
enum {
UG_BTN_LEFT = BIT(0),
UG_BTN_MIDDLE = BIT(1),
UG_BTN_RIGHT = BIT(2),
UG_BTN_4 = BIT(3),
UG_BTN_5 = BIT(4),
enum UgElemEvent {
EVENT_KEY_PRESS = 1 << 0,
EVENT_KEY_RELEASE = 1 << 1,
EVENT_KEY_HOLD = 1 << 2,
EVENT_MOUSE_HOVER = 1 << 3,
EVENT_MOUSE_PRESS = 1 << 4,
EVENT_MOUSE_RELEASE = 1 << 5,
EVENT_MOUSE_HOLD = 1 << 6,
};
// context
typedef struct {
// some style information
const ug_style_t *style;
// style_px is a style cache where all measurements are already in pixels
const ug_style_t *style_px;
// ppi: pixels per inch
// ppm: pixels per millimeter
// ppd: pixels per dot
float ppi, ppm, ppd;
float last_ppi, last_ppm, last_ppd;
// containers need to know how big the "main container" is so that all
// the relative positioning work
ug_vec2_t size;
ug_rect_t origin;
// which context and element we are hovering
ug_id_t hover_cnt;
// active is updated on mousedown and released on mouseup
// the id of the "active" element, active means different things for
// different elements, for exaple active for a button means to be pressed,
// and for a text box it means to be focused
ug_id_t active_cnt, last_active_cnt;
// id of the selected container, used for layout
// NOTE: since the stacks can be relocated with realloc it is better not
// to use a pointer here, even tough it would be better for efficiency
ug_id_t selected_cnt;
// count the frames for fun
unsigned long int frame;
// mouse data
struct {
ug_vec2_t pos;
ug_vec2_t last_pos;
ug_vec2_t delta;
ug_vec2_t scroll_delta;
// mouse.update: a mask of the mouse buttons that are being updated
unsigned char update;
// mouse.hold: a mask of the buttons that are being held
unsigned char hold;
} mouse;
// keyboard key pressed
struct {
unsigned char update;
unsigned char hold;
} key;
// input text buffer
char input_text[32];
// stacks
UG_STACK(ug_container_t) cnt_stack;
UG_STACK(ug_cmd_t) cmd_stack;
// command stack iterator
int cmd_it;
} ug_ctx_t;
// context initialization
ug_ctx_t * ug_ctx_new(void);
void ug_ctx_free(ug_ctx_t *ctx);
// updates the context with user information
// ppi: pixels per inch, the scale used for all em/mm to pixel calculations
// scale: it is the scale between the physical pixels on the screen and the pixels
// on the buffer, if unsure set to 1.0
int ug_ctx_set_displayinfo(ug_ctx_t *ctx, float scale, float ppi);
int ug_ctx_set_drawableregion(ug_ctx_t *ctx, ug_vec2_t size);
int ug_ctx_set_style(ug_ctx_t *ctx, const ug_style_t *style);
// define containers, name is used as a salt for the container id, all sizes are
// the default ones, resizing is done automagically with internal state
// a floating container can be placed anywhere and can be resized, acts like a
// window inside another window
int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div);
// like a floating container but cannot be resized
int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div);
// a menu bar is a container of fixed height, cannot be resized and sits at the
// top of the window
int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height);
// a sidebar is a variable size container anchored to one side of the window
int ug_container_sidebar(ug_ctx_t *ctx, const char *name, ug_size_t size, int side);
// a body is a container that scales with the window, sits at it's center and cannot
// be resized, it also fills all the available space
int ug_container_body(ug_ctx_t *ctx, const char *name);
// mark a conatiner for removal, it will be freed at the next frame beginning if
// name is NULL then use the selected container
int ug_container_remove(ug_ctx_t *ctx, const char *name);
// get the drawable area of the container, if name is NULL then use the selected
// container
ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name);
UgId id;
uint32_t flags;
uint32_t event;
UgRect rect;
UgElemType type;
// layouting, the following functions define how different ui elements are placed
// inside the selected container. A new element is aligned respective to the
// previous element and/or to the container, particularly elements can be placed
// in a row or a column.
int ug_layout_row(ug_ctx_t *ctx);
int ug_layout_column(ug_ctx_t *ctx);
int ug_layout_next_row(ug_ctx_t *ctx);
int ug_layout_next_column(ug_ctx_t *ctx);
// elements
int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim);
int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim);
// Input functions
int ug_input_mousemove(ug_ctx_t *ctx, int x, int y);
int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask);
int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask);
int ug_input_scroll(ug_ctx_t *ctx, int x, int y);
// TODO: other input functions
// Frame handling
int ug_frame_begin(ug_ctx_t *ctx);
int ug_frame_end(ug_ctx_t *ctx);
// Commands
// get the next command, save iteration state inside 'iterator'
ug_cmd_t * ug_cmd_next(ug_ctx_t *ctx);
// type-specific fields
union {
struct UgDiv {
enum {
DIV_LAYOUT_ROW = 0,
DIV_LAYOUT_COLUMN,
DIV_LAYOUT_FLOATING,
} layout;
UgPoint origin_r, origin_c;
UgColor color_bg;
} div; // Div
};
} UgElem;
// TODO: add a packed flag
// TODO: add a fill index to skip some searching for free spots
#undef UG_STACK
#undef BIT
typedef struct {
int size, elements;
UgId *vector; // vector of element ids
int *refs, *ordered_refs;
} UgTree;
#endif
typedef struct {
struct _IdTable *table;
UgElem *array;
uint64_t *present, *used;
int cycles;
} UgElemCache;
typedef struct _UgCtx UgCtx;
// tree implementation
int ug_tree_init(UgTree *tree, unsigned int size);
int ug_tree_pack(UgTree *tree);
int ug_tree_resize(UgTree *tree, unsigned int newsize);
int ug_tree_add(UgTree *tree, UgId elem, int parent);
int ug_tree_prune(UgTree *tree, int ref);
int ug_tree_subtree_size(UgTree *tree, int ref);
int ug_tree_children_it(UgTree *tree, int parent, int *cursor);
int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor);
int ug_tree_parentof(UgTree *tree, int node);
int ug_tree_destroy(UgTree *tree);
UgId ug_tree_get(UgTree *tree, int node);
// cache implementation
UgElemCache ug_cache_init(void);
void ug_cache_free(UgElemCache *cache);
UgElem *ug_cache_search(UgElemCache *cache, UgId id);
UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index);
int ug_init(UgCtx *ctx);
int ug_destroy(UgCtx *ctx);
int ug_frame_begin(UgCtx *ctx);
int ug_frame_end(UgCtx *ctx);
#endif // _UGUI_H

@ -0,0 +1,355 @@
#include <stdlib.h>
#include <string.h>
#include "ugui.h"
#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size)
#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0)
int ug_tree_init(UgTree *tree, unsigned int size)
{
if (tree == NULL) {
return -1;
}
tree->vector = malloc(sizeof(UgId) * size);
if (tree->vector == NULL) {
return -1;
}
tree->refs = malloc(sizeof(int) * size);
if (tree->refs == NULL) {
free(tree->vector);
return -1;
}
// ordered refs are used in the iterators
tree->ordered_refs = malloc(sizeof(int) * (size + 1));
if (tree->ordered_refs == NULL) {
free(tree->vector);
free(tree->refs);
return -1;
}
// set all refs to -1, meaning invalid (free) element
for (unsigned int i = 0; i < size; i++) {
tree->refs[i] = -1;
}
// fill vector with zeroes
memset(tree->vector, 0, size * sizeof(UgId));
tree->size = size;
tree->elements = 0;
return 0;
}
int ug_tree_destroy(UgTree *tree)
{
if (tree == NULL) {
return -1;
}
free(tree->vector);
free(tree->refs);
return 0;
}
int ug_tree_pack(UgTree *tree)
{
if (tree == NULL) {
return -1;
}
// TODO: add a PACKED flag to skip this
int free_spot = -1;
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == -1) {
free_spot = i;
continue;
}
// find a item that can be packed
if (free_spot >= 0 && tree->refs[i] >= 0) {
int old_ref = i;
// move the item
tree->vector[free_spot] = tree->vector[i];
tree->refs[free_spot] = tree->refs[i];
tree->vector[i] = 0;
tree->refs[i] = -1;
// and move all references
for (int x = 0; x < tree->size; x++) {
if (tree->refs[x] == old_ref) {
tree->refs[x] = free_spot;
}
}
// mark the free spot as used
free_spot = -1;
}
}
return 0;
}
int ug_tree_resize(UgTree *tree, unsigned int newsize)
{
if (tree == NULL) {
return -1;
}
// return error when shrinking with too many elements
if ((int)newsize < tree->elements) {
return -1;
}
// pack the vector when shrinking to avoid data loss
if ((int)newsize < tree->size) {
// if (ug_tree_pack(tree) < 0) {
// return -1;
// }
// TODO: allow shrinking, since packing destroys all references
return -1;
}
UgId *newvec = realloc(tree->vector, newsize * sizeof(UgId));
if (newvec == NULL) {
return -1;
}
int *newrefs = realloc(tree->refs, newsize * sizeof(int));
if (newrefs == NULL) {
return -1;
}
int *neworrefs = realloc(tree->ordered_refs, (newsize + 1) * sizeof(int));
if (neworrefs == NULL) {
return -1;
}
tree->vector = newvec;
tree->refs = newrefs;
tree->ordered_refs = neworrefs;
if ((int)newsize > tree->size) {
for (int i = tree->size; i < (int)newsize; i++) {
tree->vector[i] = 0;
tree->refs[i] = -1;
}
}
tree->size = newsize;
return 0;
}
// add an element to the tree, return it's ref
int ug_tree_add(UgTree *tree, UgId elem, int parent)
{
if (tree == NULL) {
return -1;
}
// invalid parent
if (!IS_VALID_REF(tree, parent)) {
return -1;
}
// no space left
if (tree->elements >= tree->size) {
return -1;
}
// check if the parent exists
// if there are no elements in the tree the first add will set the root
if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) {
return -1;
}
// get the first free spot
int free_spot = -1;
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == -1) {
free_spot = i;
break;
}
}
if (free_spot < 0) {
return -1;
}
// finally add the element
tree->vector[free_spot] = elem;
tree->refs[free_spot] = parent;
tree->elements++;
return free_spot;
}
// prune the tree starting from the ref
// returns the number of pruned elements
int ug_tree_prune(UgTree *tree, int ref)
{
if (tree == NULL) {
return -1;
}
if (!IS_VALID_REF(tree, ref)) {
return -1;
}
if (!REF_IS_PRESENT(tree, ref)) {
return 0;
}
tree->vector[ref] = 0;
tree->refs[ref] = -1;
tree->elements--;
int count = 1;
for (int i = 0; tree->elements > 0 && i < tree->size; i++) {
if (tree->refs[i] == ref) {
count += ug_tree_prune(tree, i);
}
}
return count;
}
// find the size of the subtree starting from ref
int ug_tree_subtree_size(UgTree *tree, int ref)
{
if (tree == NULL) {
return -1;
}
if (!IS_VALID_REF(tree, ref)) {
return -1;
}
if (!REF_IS_PRESENT(tree, ref)) {
return 0;
}
int count = 1;
for (int i = 0; i < tree->size; i++) {
// only root has the reference to itself
if (tree->refs[i] == ref && ref != i) {
count += ug_tree_subtree_size(tree, i);
}
}
return count;
}
// iterate through the first level children, use a cursor like strtok_r
int ug_tree_children_it(UgTree *tree, int parent, int *cursor)
{
if (tree == NULL || cursor == NULL) {
return -1;
}
// if the cursor is out of bounds then we are done for sure
if (!IS_VALID_REF(tree, *cursor)) {
return -1;
}
// same for the parent, if it's invalid it can't have children
if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) {
return -1;
}
// find the first child, update the cursor and return the ref
for (int i = *cursor; i < tree->size; i++) {
if (tree->refs[i] == parent) {
*cursor = i + 1;
return i;
}
}
// if no children are found return -1
*cursor = -1;
return -1;
}
/* iterates trough every leaf of the subtree in the following manner
* node [x], x: visit order
* [0]
* / | \
* / [2] [3]
* [1] |
* / \ [6]
* [4] [5]
*/
int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor)
{
if (tree == NULL || cursor == NULL) {
return -1;
}
int *queue = tree->ordered_refs;
// TODO: this could also be done when adding or removing elements
// first call, create a ref array ordered like we desire
if (*cursor == -1) {
*cursor = 0;
for (int i = 0; i < tree->size; i++) {
queue[i] = -1;
}
// iterate through the queue appending found children
int pos = 0, off = 0;
do {
// printf ("ref=%d\n", ref);
for (int i = 0; i < tree->size; i++) {
if (tree->refs[i] == ref) {
queue[pos++] = i;
}
}
for (; ref == queue[off] && off < tree->size; off++)
;
ref = queue[off];
} while (IS_VALID_REF(tree, ref));
// This line is why tree->ordered_refs has to be size+1
queue[off + 1] = -1;
}
// PRINT_ARR(queue, tree->size);
// return -1;
// on successive calls just iterate through the queue until we find an
// invalid ref, if the user set the cursor to -1 it means it has found what
// he needed, so free
if (*cursor < 0) {
return -1;
} else if (IS_VALID_REF(tree, *cursor)) {
return queue[(*cursor)++];
}
return -1;
}
int ug_tree_parentof(UgTree *tree, int node)
{
if (tree == NULL || !IS_VALID_REF(tree, node) ||
!REF_IS_PRESENT(tree, node)) {
return -1;
}
return tree->refs[node];
}
UgId ug_tree_get(UgTree *tree, int node)
{
if (tree == NULL || !IS_VALID_REF(tree, node)) {
return 0;
}
return tree->vector[node];
}
Loading…
Cancel
Save