parent
4aefe8b42d
commit
94837ed410
@ -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 |
@ -1,6 +1,8 @@ |
||||
microgui |
||||
ugui |
||||
*.o |
||||
test/test |
||||
**/compile_commands.json |
||||
**/.cache |
||||
test |
||||
raylib/* |
||||
|
@ -0,0 +1,108 @@ |
||||
## 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() | |
||||
+--------------+ |
||||
``` |
||||
|
||||
### 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 |
||||
} |
||||
``` |
@ -0,0 +1,14 @@ |
||||
CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/src
|
||||
CC = gcc
|
||||
LDFLAGS = -Lraylib/src -lm
|
||||
|
||||
all: ugui |
||||
|
||||
raylib/src/libraylib.a: raylib/src/Makefile |
||||
cd raylib/src; $(MAKE) PLATFORM=PLATFORM_DESKTOP
|
||||
|
||||
ugui: ugui.o vectree.o raylib/src/libraylib.a |
||||
|
||||
ugui.o: ugui.c ugui.h |
||||
|
||||
vectree.o: vectree.c ugui.h |
@ -0,0 +1,219 @@ |
||||
// 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 -> index)
|
||||
typedef struct { |
||||
uint32_t id; |
||||
uint32_t index; |
||||
} IdElem; |
||||
|
||||
typedef struct { |
||||
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, uint32_t 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, uint32_t 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) |
||||
|
||||
typedef struct { |
||||
IdTable *table; |
||||
UgElem *array; |
||||
uint64_t *present, *used; |
||||
int cycles; |
||||
} UgElemCache; |
||||
|
||||
/* 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); |
||||
} |
||||
} |
||||
|
||||
const UgElem *ug_cache_search(UgElemCache *cache, uint32_t 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; |
||||
} |
||||
|
||||
const 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
|
||||
const UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, int32_t *index) |
||||
{ |
||||
*index = ug_cache_get_free_spot(cache); |
||||
return ug_cache_insert_at(cache, g, *index); |
||||
} |
||||
|
@ -0,0 +1,5 @@ |
||||
-Iraylib/src |
||||
-Wall |
||||
-Wextra |
||||
-pedantic |
||||
-std=c11 |
@ -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 |
||||
|
@ -0,0 +1,61 @@ |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <stdint.h> |
||||
#include <string.h> |
||||
|
||||
#include "raylib.h" |
||||
#include "ugui.h" |
||||
|
||||
int main(void) |
||||
{ |
||||
UgCtx ctx; |
||||
ug_init(&ctx); |
||||
|
||||
InitWindow(800, 450, "Ugui Test"); |
||||
|
||||
// Main loop
|
||||
while (!WindowShouldClose()) { |
||||
ug_begin_frame(&ctx); |
||||
|
||||
// drawing
|
||||
BeginDrawing(); |
||||
ClearBackground(BLACK); |
||||
DrawText( |
||||
"Congrats! You created your first window!", |
||||
190, |
||||
200, |
||||
20, |
||||
LIGHTGRAY |
||||
); |
||||
EndDrawing(); |
||||
|
||||
ug_end_frame(&ctx); |
||||
} |
||||
|
||||
CloseWindow(); |
||||
|
||||
ug_destroy(&ctx); |
||||
return 0; |
||||
} |
||||
|
||||
int ug_init(UgCtx *ctx) |
||||
{ |
||||
if (ctx == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
ug_tree_init(&ctx->tree, 10); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int ug_destroy(UgCtx *ctx) |
||||
{ |
||||
if (ctx == NULL) { |
||||
return -1; |
||||
} |
||||
|
||||
ug_tree_destroy(&ctx->tree); |
||||
|
||||
return 0; |
||||
} |
@ -0,0 +1,63 @@ |
||||
#ifndef _UGUI_H |
||||
#define _UGUI_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
typedef struct { |
||||
int32_t x, y, w, h; |
||||
} UgRect; |
||||
|
||||
typedef struct { |
||||
int32_t x, y; |
||||
} UgPoint; |
||||
|
||||
typedef struct { |
||||
uint8_t r, g, b, a; |
||||
} UgColor; |
||||
|
||||
typedef enum { |
||||
ETYPE_NONE = 0, |
||||
ETYPE_BUTTON, |
||||
ETYPE_TEXT, |
||||
ETYPE_SCROLL, |
||||
ETYPE_SLIDER, |
||||
} UgElemType; |
||||
|
||||
typedef struct { |
||||
uint64_t id; |
||||
UgRect rec; |
||||
union { |
||||
uint32_t type_int; |
||||
UgElemType type; |
||||
}; |
||||
} UgElem; |
||||
|
||||
// TODO: add a packed flag
|
||||
// TODO: add a fill index to skip some searching for free spots
|
||||
|
||||
typedef struct { |
||||
int size, elements; |
||||
uint32_t *vector; // vector of element ids
|
||||
int *refs, *ordered_refs; |
||||
} UgTree; |
||||
|
||||
typedef struct { |
||||
UgTree tree; |
||||
} 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, uint32_t 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_destroy(UgTree *tree); |
||||
|
||||
int ug_init(UgCtx *ctx); |
||||
int ug_destroy(UgCtx *ctx); |
||||
|
||||
#endif // _UGUI_H
|
||||
|
@ -0,0 +1,335 @@ |
||||
#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(UgElem) * 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); |
||||
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(UgElem)); |
||||
|
||||
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; |
||||
} |
||||
|
||||
uint32_t *newvec = realloc(tree->vector, newsize * sizeof(uint32_t)); |
||||
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 * 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, uint32_t 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; |
||||
|
||||
int count = 1; |
||||
for (int i = 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 (queue == NULL) { |
||||
*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)); |
||||
} |
||||
|
||||
// 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; |
||||
} |
Loading…
Reference in new issue