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 |
microgui |
||||||
|
ugui |
||||||
*.o |
*.o |
||||||
test/test |
test/test |
||||||
**/compile_commands.json |
**/compile_commands.json |
||||||
**/.cache |
**/.cache |
||||||
test |
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