diff --git a/.clang-format b/.clang-format deleted file mode 100644 index b6d0dcb..0000000 --- a/.clang-format +++ /dev/null @@ -1,41 +0,0 @@ -# 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 diff --git a/.gitignore b/.gitignore index 15fae14..228cff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,2 @@ -microgui -ugui *.o -test/test -**/compile_commands.json -**/.cache -test -raylib/* +build/* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0bc165c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/raylib.c3l"] + path = lib/raylib.c3l + url = https://github.com/NexushasTaken/raylib.c3l diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile deleted file mode 100644 index 0d82e80..0000000 --- a/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cache.c b/cache.c deleted file mode 100644 index 5195892..0000000 --- a/cache.c +++ /dev/null @@ -1,211 +0,0 @@ -// 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 -#include - -#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); -} diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index 3e90386..0000000 --- a/compile_flags.txt +++ /dev/null @@ -1,5 +0,0 @@ --Iraylib/include --Wall --Wextra --pedantic --std=c11 diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/fm/README b/fm/README deleted file mode 100644 index 3c09197..0000000 --- a/fm/README +++ /dev/null @@ -1 +0,0 @@ -File Manager using ugui diff --git a/fm/libconf.c b/fm/libconf.c deleted file mode 100644 index 07ac86e..0000000 --- a/fm/libconf.c +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include - -#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 diff --git a/fm/libconf.h b/fm/libconf.h deleted file mode 100644 index f92c29b..0000000 --- a/fm/libconf.h +++ /dev/null @@ -1,7 +0,0 @@ -#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 diff --git a/get_raylib.sh b/get_raylib.sh deleted file mode 100755 index cbe25df..0000000 --- a/get_raylib.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/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 diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/raylib.c3l b/lib/raylib.c3l new file mode 160000 index 0000000..c7ebe05 --- /dev/null +++ b/lib/raylib.c3l @@ -0,0 +1 @@ +Subproject commit c7ebe054ce16136c1128fab54fcce4921044293e diff --git a/project.json b/project.json new file mode 100644 index 0000000..463cd65 --- /dev/null +++ b/project.json @@ -0,0 +1,56 @@ +{ + // Language version of C3. + "langrev": "1", + // Warnings used for all targets. + "warnings": [ "no-unused" ], + // Directories where C3 library files may be found. + "dependency-search-paths": [ "lib" ], + // Libraries to use for all targets. + "dependencies": [ "raylib" ], + "features": [ + // See rcore.c3 + //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", + //"SUPPORT_STANDARD_FILEIO", + //"SUPPORT_FILE_SYSTEM_FUNCTIONS", + //"SUPPORT_DATA_ENCODER", + // See text.c3 + //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", + //"SUPPORT_TEXT_C_STRING_MANAGEMENT", + //"SUPPORT_RANDOM_GENERATION", + + "SUPPORT_RAYGUI", + //"RAYGUI_NO_ICONS", + //"RAYGUI_CUSTOM_ICONS", + ], + // Authors, optionally with email. + "authors": [ "John Doe " ], + // Version using semantic versioning. + "version": "0.1.0", + // Sources compiled for all targets. + "sources": [ "src/**" ], + // C sources if the project also compiles C sources + // relative to the project file. + // "c-sources": [ "csource/**" ], + // Include directories for C sources relative to the project file. + // "c-include-dirs": [ "csource/include" ], + // Output location, relative to project file. + "output": "build", + // Architecture and OS target. + // You can use 'c3c --list-targets' to list all valid targets. + // "target": "windows-x64", + // Targets. + "targets": { + "ugui": { + // Executable or library. + "type": "executable", + // Additional libraries, sources + // and overrides of global settings here. + }, + }, + // Global settings. + // CPU name, used for optimizations in the LLVM backend. + "cpu": "generic", + // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". + "opt": "O0", + // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. +} diff --git a/resources/.gitkeep b/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/cache.c3 b/src/cache.c3 new file mode 100644 index 0000000..2123194 --- /dev/null +++ b/src/cache.c3 @@ -0,0 +1,133 @@ +module cache(); + +/* LRU Cache + * The cache uses a pool (array) to store all the elements, each element has + * a key (id) and a value. A HashMap correlates the ids to an index in the pool. + * To keep track of which items were recently used two bit arrays are kept, one + * stores the "used" flag for each index and anothe the "present" flag. + * Every NCYCLES operations the present and used arrays are updated to free up + * the elements that were not recently used. + */ + +// FIXME: this module should really allocate all resources on an arena or temp +// allocator, since all memory allocations are connected and freeing +// happens at the same time + +import std::core::mem; +import std::collections::bitset; +import std::collections::map; + +def BitArr = bitset::BitSet() @private; +def IdTable = map::Map() @private; +def IdTableEntry = map::Entry() @private; + +const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); + +struct Cache { + BitArr present, used; + IdTable table; + Value[] pool; + usz cycle_count; +} + +// Every CACHE_CYCLES operations mark as not-present the unused elements +macro Cache.cycle(&cache) @private { + cache.cycle_count++; + if (cache.cycle_count > CACHE_NCYCLES) { + for (usz i = 0; i < cache.present.data.len; i++) { + cache.present.data[i] &= cache.used.data[i]; + cache.used.data[i] = 0; + } + cache.cycle_count = 0; + } +} + +fn void! Cache.init(&cache) +{ + cache.table = map::new()(SIZE); + // FIXME: this shit is SLOW + foreach (idx, bit : cache.used) { cache.used[idx] = false; } + foreach (idx, bit : cache.present) { cache.present[idx] = false; } + cache.pool = mem::new_array(Value, SIZE); +} + +fn void Cache.free(&cache) +{ + (void)cache.table.free(); + (void)mem::free(cache.pool); +} + +fn Value*! Cache.search(&cache, Key id) +{ + // get_entry() faults on miss + IdTableEntry* entry = cache.table.get_entry(id)!; + + /* MISS */ + if (entry.key != id) { + return SearchResult.MISSING?; + } + + /* MISS, the data is not valid (not present) */ + if (!cache.present[entry.value]) { + // if the data is not present but it is still in the table, remove it + cache.table.remove(id)!; + return SearchResult.MISSING?; + } + + /* HIT, set as recently used */ + cache.used[entry.value] = true; + return &(cache.pool[entry.value]); +} + +/* 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 */ +fn usz Cache.get_free_spot(&cache) @private +{ + // FIXME: This shit is SLOW, especially when clz() exists + foreach (idx, bit: cache.present) { + if (bit == false) { + return idx; + } + } + return 0; +} + +fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private +{ + // TODO: verify index, g and id + Value* spot; + + /* Set used and present */ + cache.present.set(index); + cache.used.set(index); + cache.cycle(); + + spot = &(cache.pool[index]); + *spot = *g; + cache.table.set(id, index); + return spot; +} + +// Insert an element in the cache, returns the index +fn Value*! Cache.insert_new(&cache, Value* g, Key id) +{ + usz index = cache.get_free_spot(); + return cache.insert_at(g, id, index); +} + +fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) +{ + Value*! c = cache.search(id); + if (catch e = c) { + if (e != SearchResult.MISSING) { + return e?; + } else { + // if the element is new (inserted) set the is_new flag + if (is_new) *is_new = true; + return cache.insert_new(g, id); + } + } else { + if (is_new) *is_new = false; + return c; + } +} diff --git a/src/fifo.c3 b/src/fifo.c3 new file mode 100644 index 0000000..dbc50f0 --- /dev/null +++ b/src/fifo.c3 @@ -0,0 +1,49 @@ +module fifo(); + +import std::core::mem; + +fault FifoErr { + FULL, + EMPTY, +} + +// TODO: specify the allocator + +struct Fifo { + Type[] arr; + usz out; + usz count; +} + +fn void! Fifo.init(&fifo, usz size) +{ + fifo.arr = mem::new_array(Type, size); + fifo.out = 0; + fifo.count = 0; +} + +fn void Fifo.free(&fifo) +{ + (void)mem::free(fifo.arr); +} + +fn void! Fifo.enqueue(&fifo, Type *elem) +{ + if (fifo.count >= fifo.arr.len) { + return FifoErr.FULL?; + } + usz in = (fifo.out + fifo.count) % fifo.arr.len; + fifo.arr[in] = *elem; + fifo.count++; +} + +fn Type*! Fifo.dequeue(&fifo) +{ + if (fifo.count == 0) { + return FifoErr.EMPTY?; + } + Type *ret = &fifo.arr[fifo.out]; + fifo.count--; + fifo.out = (fifo.out + 1) % fifo.arr.len; + return ret; +} diff --git a/src/main.c3 b/src/main.c3 new file mode 100644 index 0000000..989e1d7 --- /dev/null +++ b/src/main.c3 @@ -0,0 +1,196 @@ +import std::io; +import vtree; +import cache; +import ugui; +import rl; + +fn int main(String[] args) +{ + ugui::Ctx ctx; + ctx.init()!!; + + short width = 800; + short height = 450; + rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); + rl::init_window(width, height, "Ugui Test"); + ctx.input_window_size(width, height)!!; + + double[10][4] median_times; + isz frame; + double median_input, median_layout, median_draw, median_tot; + + // Main loop + while (!rl::window_should_close()) { + const int PARTIAL_INPUT = 0; + const int PARTIAL_LAYOUT = 1; + const int PARTIAL_DRAW = 2; + //timer_start(); + + /*** Start Input Handling ***/ + if (rl::is_window_resized()) { + width = (short)rl::get_screen_width(); + height = (short)rl::get_screen_height(); + ctx.input_window_size(width, height)!!; + } + + ctx.input_changefocus(rl::is_window_focused()); + + // FIXME: In raylib it doesn't seem to be a quick way to check if + // a mouse input event was received, so for now just use + // the delta information + rl::Vector2 mousedelta = rl::get_mouse_delta(); + if (mousedelta.x || mousedelta.y) { + ctx.input_mouse_delta((short)mousedelta.x, (short)mousedelta.y); + } + + ugui::MouseButtons buttons; + buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT); + buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); + buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); + ctx.input_mouse_button(buttons); + + //timer_partial(PARTIAL_INPUT); + /*** End Input Handling ***/ + + /*** Start UI Handling ***/ + ctx.frame_begin()!!; + + // main div, fill the whole window + ctx.div_begin("main", ugui::DIV_FILL)!!; + {| + ctx.layout_set_column()!!; + if (ctx.button("button0", ugui::Rect{.y = 100, .x = 100, .w = 30, .h = 30})!!.mouse_hold) { + io::printn("HOLDING button0"); + } + ctx.layout_next_column()!!; + ctx.button("button1", ugui::Rect{.w = 30, .h = 30})!!; + ctx.layout_next_column()!!; + ctx.button("button2", ugui::Rect{.w = 30, .h = 30})!!; + |}; + ctx.div_end()!!; + + ctx.frame_end()!!; + //timer_partial(PARTIAL_LAYOUT); + /*** End UI Handling ***/ + + /*** Start UI Drawing ***/ + rl::begin_drawing(); + // ClearBackground(BLACK); + + io::printn("----- Draw Begin -----"); + rl::Color c; + for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { + switch (cmd.type) { + case ugui::CmdType.CMD_RECT: + io::printfn( + "draw rect x=%d y=%d w=%d h=%d", + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h + ); + c = rl::Color{ + .r = cmd.rect.color.r, + .g = cmd.rect.color.g, + .b = cmd.rect.color.b, + .a = cmd.rect.color.a, + }; + rl::draw_rectangle( + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h, + c + ); + default: + io::printfn("Unknown cmd type: %d", cmd.type); + } + } + io::printf("----- Draw End -----\n\n"); + + rl::end_drawing(); + //timer_partial(PARTIAL_DRAW); + //timer_stop(); + /*** End UI Drawing ***/ + /* + median_times[frame][PARTIAL_INPUT] = + 1e3 * timer_get_sec(PARTIAL_INPUT); + median_times[frame][PARTIAL_LAYOUT] = + 1e3 * timer_get_sec(PARTIAL_LAYOUT); + median_times[frame][PARTIAL_DRAW] = + 1e3 * timer_get_sec(PARTIAL_DRAW); + median_times[frame][3] = 1e3 * timer_get_sec(-1); + */ + frame += 1; + frame %= 10; + /* + if (frame == 0) { + median_input = 0; + median_layout = 0; + median_draw = 0; + median_tot = 0; + for (size_t i = 0; i < 10; i++) { + median_input += median_times[i][PARTIAL_INPUT]; + median_layout += median_times[i][PARTIAL_LAYOUT]; + median_draw += median_times[i][PARTIAL_DRAW]; + median_tot += median_times[i][3]; + } + median_input /= 10; + median_layout /= 10; + median_draw /= 10; + median_tot /= 10; + } + + printf("input time: %lfms\n", median_input); + printf("layout time: %lfms\n", median_layout); + printf("draw time: %lfms\n", median_draw); + printf("total time: %lfms\n", median_tot); + + // Throttle Frames + // TODO: add an fps limit, time frame generation and log it + const float TARGET_FPS = 100; + float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); + WaitTime(wait_time); + */ + } + + rl::close_window(); + + ctx.free(); + return 0; +} + +fn void! test_vtree() @test +{ + vtree::VTree() vt; + vt.init(10)!!; + defer vt.free(); + + assert(vt.size() == 10, "Size is incorrect"); + + isz ref = vt.add("Ciao Mamma", 0)!!; + String s = vt.get(ref)!!; + assert(s == "Ciao Mamma", "String is incorrect"); + isz par = vt.parentof(0)!!; + assert(ref == par, "Not Root"); + + vt.print(); +} + +def StrCache = cache::Cache(); +fn void! test_cache() @test +{ + StrCache cc; + cc.init()!!; + defer cc.free(); + + String*! r = cc.search(1); + if (catch ex = r) { + if (ex != SearchResult.MISSING) { + return ex?; + } + } + + r = cc.get_or_insert(&&"Ciao Mamma", 1)!; + assert(*r!! == "Ciao Mamma", "incorrect string"); +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 new file mode 100644 index 0000000..7883b62 --- /dev/null +++ b/src/ugui_data.c3 @@ -0,0 +1,184 @@ +module ugui; + +import vtree; +import cache; +import fifo; + +struct Rect { + short x, y, w, h; +} + +struct Point { + short x, y; +} + +struct Color{ + char r, g, b, a; +} + +// element ids are just long ints +def Id = usz; + +enum ElemType { + ETYPE_NONE, + ETYPE_DIV, + ETYPE_BUTTON, +} + +bitstruct ElemFlags : uint { + bool updated : 0; + bool has_focus : 1; +} + +bitstruct ElemEvents : uint { + bool key_press : 0; + bool key_release : 1; + bool key_hold : 2; + bool mouse_hover : 3; + bool mouse_press : 4; + bool mouse_release : 5; + bool mouse_hold : 6; +} + +enum DivLayout { + LAYOUT_ROW, + LAYOUT_COLUMN, + LAYOUT_FLOATING, +} + +// div element +struct Div { + DivLayout layout; + Point origin_r, origin_c; + Color color_bg; +} + +// element structure +struct Elem { + Id id; + ElemFlags flags; + ElemEvents events; + Rect rect; + ElemType type; + union { + Div div; + } +} + + +// relationships between elements are stored in a tree, it stores just the ids +def IdTree = vtree::VTree() @private; + +// elements themselves are kept in a cache +const uint MAX_ELEMENTS = 2048; +def ElemCache = cache::Cache() @private; + +def CmdQueue = fifo::Fifo(); + +fault UgError { + INVALID_SIZE, + EVENT_UNSUPPORTED, + UNEXPECTED_ELEMENT, +} + +fn Id fnv1a(String str) +{ + const ulong FNV_OFF = 0xcbf29ce484222325; + const ulong FNV_PRIME = 0x100000001b3; + + ulong hash = FNV_OFF; + foreach (c : str) { + hash ^= c; + hash *= FNV_PRIME; + } + + return hash; +} + +macro hash(String str) { return fnv1a(str); } + +macro uint_to_rgba(uint u) { + return Color{ + .r = (char)((u >> 24) & 0xff), + .g = (char)((u >> 16) & 0xff), + .b = (char)((u >> 8) & 0xff), + .a = (char)((u >> 0) & 0xff) + }; +} + +const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; + +macro abs(a) { return a < 0 ? -a : a; } +macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); } + +const uint STACK_STEP = 10; +const uint MAX_ELEMS = 128; +const uint MAX_CMDS = 256; +const uint ROOT_ID = 1; + +// command type +enum CmdType { + CMD_RECT, +} + +// command to draw a rect +struct CmdRect { + Rect rect; + Color color; +} + +// command structure +struct Cmd { + CmdType type; + union { + CmdRect rect; + } +} + +enum Layout { + ROW, + COLUMN, + FLOATING +} + +// global style, similar to the css box model +struct Style { // css box model + Rect padding; + Rect border; + Rect margin; + Color bgcolor; // background color + Color fgcolor; // foreground color + Color bcolor; // border color +} + + +struct Ctx { + Layout layout; + IdTree tree; + ElemCache cache; + CmdQueue cmd_queue; + // total size in pixels of the context + ushort width, height; + Style style; + + bool has_focus; + struct input { + InputEvents events; + struct mouse { + Point pos, delta; + // mouse_down: bitmap of mouse buttons that are held + // mouse_updated: bitmap of mouse buttons that have been updated + // mouse_released = mouse_updated & ~mouse_down + // mouse_pressed = mouse_updated & mouse_down + MouseButtons down; + MouseButtons updated; + } + } + + isz active_div; // tree node indicating the current active div +} + +macro point_in_rect(Point p, Rect r) +{ + return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); +} diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 new file mode 100644 index 0000000..f2d3dca --- /dev/null +++ b/src/ugui_impl.c3 @@ -0,0 +1,244 @@ +module ugui; + +import std::io; + +// return a pointer to the parent of the current active div +fn Elem*! Ctx.get_parent(&ctx) +{ + // FIXME: if the tree held pointers to the elements then no more + // redundant cache search + Id parent_id = ctx.tree.get(ctx.active_div)!; + return ctx.cache.search(parent_id); +} + +fn void! Ctx.init(&ctx) +{ + ctx.tree.init(MAX_ELEMENTS)!; + defer catch { (void)ctx.tree.free(); } + //ug_fifo_init(&ctx.fifo, MAX_CMDS); + + ctx.cache.init()!; + defer catch { (void)ctx.cache.free(); } + + ctx.cmd_queue.init(MAX_ELEMENTS)!; + defer catch { (void)ctx.cmd_queue.free(); } + + ctx.layout = Layout.ROW; + ctx.active_div = 0; + + // TODO: add style config + ctx.style.margin = Rect{1, 1, 1, 1}; +} + +fn void Ctx.free(&ctx) +{ + (void)ctx.tree.free(); + (void)ctx.cache.free(); + (void)ctx.cmd_queue.free(); +} + +fn void! Ctx.frame_begin(&ctx) +{ + // 1. Create the root div element + // NOTE: in c3 everythong is zero initialized by default + Rect space = { + .w = ctx.width, + .h = ctx.height, + }; + + Elem root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .rect = space, + .div = { + .layout = LAYOUT_ROW, + }, + }; + + // The root should have the updated flag only if the size of the window + // was changed between frames, this propagates an element size recalculation + // down the element tree + if (ctx.input.events.resize) { + root.flags.updated = true; + } + + // if the window has focus then the root element also has focus, no other + // computation needed, child elements need to check the mouse positon and + // other stuff + if (ctx.has_focus) { + root.flags.has_focus = true; + } + + // FIXME: check errors + // 2. Get the root element from the cache and update it + bool is_new; + Elem empty_elem; + Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, root.id, &is_new)!; + // flags always need to be set to the new flags + c_elem.flags = root.flags; + if (is_new || root.flags.updated) { + *c_elem = root; + } + + // 3. Push the root element into the element tree + ctx.active_div = ctx.tree.add(root.id, 0)!; + + // print_tree(ctx); + + // The root element does not push anything to the stack + // TODO: add a background color taken from a theme or config + + io::printn("##### Frame Begin #####"); +} + +fn void! Ctx.frame_end(&ctx) +{ + // 1. clear the tree + ctx.tree.prune(0)!; + + // 2. clear input fields + ctx.input.events = (InputEvents)0; + + // draw mouse position +$if 1: + Cmd cmd = { + .type = CMD_RECT, + .rect.rect = { + .x = ctx.input.mouse.pos.x - 2, + .y = ctx.input.mouse.pos.y - 2, + .w = 4, + .h = 4, + }, + .rect.color = uint_to_rgba(0xff00ffff) + }; + ctx.cmd_queue.enqueue(&cmd)!; +$endif + + io::printn("##### Frame End #####"); +} + +fn void! Ctx.div_begin(&ctx, String label, Rect size) +{ + Id id = hash(label); + + bool is_new; + Elem empty_elem; + Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + + // FIXME: why save the id in the tree and not something more direct like + // the element pointer or the index into the cache vector? + isz div_node = ctx.tree.add(id, ctx.active_div)!; + + Elem *parent = ctx.get_parent()!; + + ctx.tree.print(); + + // Use the current div + ctx.active_div = div_node; + + // 1. Fill the element fields + // this resets the flags + c_elem.type = ETYPE_DIV; + c_elem.flags = (ElemFlags)0; + + // do layout and update flags only if the element was updated + if (is_new || parent.flags.updated) { + // 2. layout the element + c_elem.rect = ctx.position_element(parent, size); + + // 3. Mark the element as updated + c_elem.flags.updated = true; + // 4. Fill the div fields + c_elem.div.layout = parent.div.layout; + c_elem.div.origin_c = Point{ + .x = c_elem.rect.x, + .y = c_elem.rect.y, + }; + c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.origin_r = c_elem.div.origin_c; + } else if (parent.flags.has_focus) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.rect)) { + c_elem.flags.has_focus = true; + } + } else { + // TODO: check active + // TODO: check scrollbars + // TODO: check resizeable + } + + // Add the background to the draw stack + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = c_elem.div.color_bg, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; +} + +fn void! Ctx.div_end(&ctx) +{ + // the active_div returns to the parent of the current one + ctx.active_div = ctx.tree.parentof(ctx.active_div)!; +} + +// @ensure elem != null +fn bool Ctx.is_hovered(&ctx, Elem *elem) +{ + return point_in_rect(ctx.input.mouse.pos, elem.rect); +} + +fn ElemEvents! Ctx.button(&ctx, String label, Rect size) +{ + Id id = hash(label); + + // TODO: do layouting if the element is new or the parent has updated + bool is_new; + Elem empty_elem; + Elem *c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + ctx.tree.print(); + + Elem *parent = ctx.get_parent()!; + + // 1. Fill the element fields + // this resets the flags + c_elem.type = ETYPE_BUTTON; + c_elem.flags = (ElemFlags)0; + Color bg_color = uint_to_rgba(0x0000ffff); + + // if the element is new or the parent was updated then redo layout + if (is_new || parent.flags.updated) { + // 2. Layout + c_elem.rect = ctx.position_element(parent, size, true); + + // TODO: 3. Fill the button specific fields + } + + // TODO: Check for interactions + if (parent.flags.has_focus) { + if (ctx.is_hovered(c_elem)) { + c_elem.flags.has_focus = true; + c_elem.events.mouse_hover = true; + bg_color = uint_to_rgba(0x00ff00ff); + c_elem.events.mouse_hold = ctx.input.mouse.down.btn_left; + } else { + c_elem.events.mouse_hover = false; + } + } + + // Draw the button + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = bg_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + return c_elem.events; +} diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 new file mode 100644 index 0000000..d8c63f3 --- /dev/null +++ b/src/ugui_input.c3 @@ -0,0 +1,78 @@ +module ugui; + +import std::io; + +// TODO: this could be a bitstruct +bitstruct InputEvents : uint { + bool resize : 0; // window size was changed + bool change_focus : 1; // window focus changed + bool mouse_move : 2; // mouse was moved + bool mouse_btn : 3; // mouse button pressed or released +} + +// Window size was changed +fn void! Ctx.input_window_size(&ctx, short width, short height) +{ + if (width <= 0 || height <= 0) { + return UgError.INVALID_SIZE?; + } + ctx.width = width; + ctx.height = height; + ctx.input.events.resize = true; +} + +// Window gained/lost focus +fn void Ctx.input_changefocus(&ctx, bool has_focus) +{ + // FIXME: raylib only has an API to query the focus status so we have to + // update the input flag only if the focus changed + if (ctx.has_focus != has_focus) { + ctx.input.events.change_focus = true; + } + ctx.has_focus = has_focus; +} + +bitstruct MouseButtons : uint { + bool btn_left : 0; + bool btn_middle : 1; + bool btn_right : 2; + bool btn_4 : 3; + bool btn_5 : 4; +} + +// Mouse Button moved +fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) +{ + ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons; + ctx.input.mouse.down = buttons; + ctx.input.events.mouse_btn = true; + + io::printfn( + "Mouse Down: %s%s%s%s%s", + buttons.btn_left ? "BTN_LEFT " : "", + buttons.btn_right ? "BTN_RIGHT " : "", + buttons.btn_middle ? "BTN_MIDDLE " : "", + buttons.btn_4 ? "BTN_4 " : "", + buttons.btn_5 ? "BTN_5 " : "" + ); +} + +// Mouse was moved, report absolute position +// TODO: implement this +fn void Ctx.input_mouse_abs(&ctx, short x, short y) { return; } + +// Mouse was moved, report relative motion +fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) +{ + ctx.input.mouse.delta.x = dx; + ctx.input.mouse.delta.y = dy; + + short mx, my; + mx = ctx.input.mouse.pos.x + dx; + my = ctx.input.mouse.pos.y + dy; + + ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width); + ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height); + + ctx.input.events.mouse_move = true; +} diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 new file mode 100644 index 0000000..5c38c17 --- /dev/null +++ b/src/ugui_layout.c3 @@ -0,0 +1,153 @@ +module ugui; + + +fn void! Ctx.layout_set_row(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.layout = LAYOUT_ROW; +} + +fn void! Ctx.layout_set_column(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.layout = LAYOUT_COLUMN; +} + +fn void! Ctx.layout_set_floating(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.layout = LAYOUT_FLOATING; +} + +fn void! Ctx.layout_next_row(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.origin_r = Point{ + .x = parent.rect.x, + .y = parent.div.origin_c.y, + }; + parent.div.origin_c = parent.div.origin_r; +} + +fn void! Ctx.layout_next_column(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.origin_c = Point{ + .x = parent.div.origin_r.x, + .y = parent.rect.y, + }; + parent.div.origin_r = parent.div.origin_c; +} + +// position the rectangle inside the parent according to the layout +fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) +{ + Rect elem_rect; + Point origin; + + // 1. Select the right origin + switch (parent.div.layout) { + case LAYOUT_ROW: + origin = parent.div.origin_r; + case LAYOUT_COLUMN: + origin = parent.div.origin_c; + case LAYOUT_FLOATING: // none + default: + // Error + } + + // 2. Position the rect + elem_rect.x = origin.x + rect.x; + elem_rect.y = origin.y + rect.y; + + // 3. Calculate width & height + // TODO: what about negative values? + // FIXME: account for origin offset!! + elem_rect.w = rect.w > 0 ? rect.w : parent.rect.w; + elem_rect.h = rect.h > 0 ? rect.h : parent.rect.h; + + // 4. Update the origins of the parent + parent.div.origin_r = Point{ + .x = elem_rect.x + elem_rect.w, + .y = elem_rect.y, + }; + parent.div.origin_c = Point{ + .x = elem_rect.x, + .y = elem_rect.y + elem_rect.h, + }; + + // if using the style then apply margins + // FIXME: this does not work + if (style && parent.div.layout != LAYOUT_FLOATING) { + elem_rect.x += ctx.style.margin.x; + elem_rect.y += ctx.style.margin.y; + + // total keep-out borders + Rect margin_tot = { + .x = ctx.style.padding.x + ctx.style.border.x + + ctx.style.margin.x, + .y = ctx.style.padding.y + ctx.style.border.y + + ctx.style.margin.y, + .w = ctx.style.padding.w + ctx.style.border.x + + ctx.style.margin.w, + .h = ctx.style.padding.h + ctx.style.border.x + + ctx.style.margin.h, + }; + + parent.div.origin_r.x += margin_tot.x + margin_tot.w; + // parent.div.origin_r.y += margin_tot.h; + + // parent.div.origin_c.x += margin_tot.w; + parent.div.origin_c.y += margin_tot.y + margin_tot.h; + } + + /* + printf( + "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d + %d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.rect.x, + parent.rect.y, + parent.rect.w, + parent.rect.h, + elem_rect.x, + elem_rect.y, + elem_rect.w, + elem_rect.h + ); + */ + return elem_rect; +} diff --git a/src/vtree.c3 b/src/vtree.c3 new file mode 100644 index 0000000..8e5d7b1 --- /dev/null +++ b/src/vtree.c3 @@ -0,0 +1,333 @@ +module vtree(); + +import std::core::mem; +import std::io; + +struct VTree { + usz elements; + ElemType[] vector; // vector of element ids + isz[] refs, ordered_refs; +} + +fault VTreeError { + CANNOT_SHRINK, + INVALID_REFERENCE, + TREE_FULL, + REFERENCE_NOT_PRESENT, + INVALID_ARGUMENT, +} + +macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } +macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } +macro VTree.size(&tree) { return tree.refs.len; } + +// macro to zero an elemen +macro @zero() +{ + $if $assignable(0, ElemType): + return 0; + $else + return ElemType{0}; + $endif +} + +fn void! VTree.init(&tree, usz size) +{ + tree.vector = mem::new_array(ElemType, size); + defer catch { (void)mem::free(tree.vector); } + + tree.refs = mem::new_array(isz, size); + defer catch { (void)mem::free(tree.refs); } + + tree.ordered_refs = mem::new_array(isz, size); + defer catch { (void)mem::free(tree.ordered_refs); } + + // set all refs to -1, meaning invalid (free) element + tree.refs[..] = -1; + + tree.elements = 0; +} + +fn void VTree.free(&tree) +{ + (void)mem::free(tree.vector); + (void)mem::free(tree.refs); + (void)mem::free(tree.ordered_refs); +} + +fn void VTree.pack(&tree) +{ + // TODO: add a PACKED flag to skip this + + isz free_spot = -1; + for (usz 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) { + isz old_ref = i; + + // move the item + tree.vector[free_spot] = tree.vector[i]; + tree.refs[free_spot] = tree.refs[i]; + + tree.vector[i] = @zero(); + tree.refs[i] = -1; + + // and move all references + for (usz j = 0; j < tree.size(); j++) { + if (tree.refs[j] == old_ref) { + tree.refs[j] = free_spot; + } + } + + // mark the free spot as used + free_spot = -1; + } + } +} + +fn void! VTree.resize(&tree, usz newsize) +{ + // return error when shrinking with too many elements + if (newsize < tree.elements) { + return VTreeError.CANNOT_SHRINK?; + } + + // pack the vector when shrinking to avoid data loss + if ((int)newsize < tree.size()) { + // FIXME: packing destroys all references to elements of vec + // so shrinking may cause dangling pointers + return VTreeError.CANNOT_SHRINK?; + } + + usz old_size = tree.size(); + + tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize]; + defer catch { (void)mem::free(tree.vector); } + + tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize]; + defer catch { (void)mem::free(tree.refs); } + + tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize]; + defer catch { (void)mem::free(tree.ordered_refs); } + + if (newsize > tree.size()) { + tree.vector[old_size..newsize-1] = @zero(); + tree.refs[old_size..newsize-1] = -1; + } +} + +// add an element to the tree, return it's ref +fn isz! VTree.add(&tree, ElemType elem, isz parent) +{ + // invalid parent + if (!tree.ref_is_valid(parent)) { + return VTreeError.INVALID_REFERENCE?; + } + + // no space left + if (tree.elements >= tree.size()) { + return VTreeError.TREE_FULL?; + } + + // check if the parent exists + // if there are no elements in the tree the first add will set the root + if (!tree.ref_is_present(parent) && tree.elements != 0) { + return VTreeError.REFERENCE_NOT_PRESENT?; + } + + // get the first free spot + isz free_spot = -1; + for (usz i = 0; i < tree.size(); i++) { + if (tree.refs[i] == -1) { + free_spot = i; + break; + } + } + if (free_spot < 0) { + return VTreeError.TREE_FULL?; + } + + // 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 +fn usz! VTree.prune(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return 0; + } + + tree.vector[ref] = @zero(); + tree.refs[ref] = -1; + tree.elements--; + + usz count = 1; + for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) { + if (tree.refs[i] == ref) { + count += tree.prune(i)!; + } + } + + return count; +} + +// find the size of the subtree starting from ref +fn usz! VTree.subtree_size(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return 0; + } + + usz count = 1; + for (usz i = 0; i < tree.size(); i++) { + // only root has the reference to itself + if (tree.refs[i] == ref && ref != i) { + count += tree.subtree_size(i)!; + } + } + + return count; +} + +// iterate through the first level children, use a cursor like strtok_r +fn isz! VTree.children_it(&tree, isz parent, isz *cursor) +{ + if (cursor == null) { + return VTreeError.INVALID_ARGUMENT?; + } + + // if the cursor is out of bounds then we are done for sure + if (!tree.ref_is_valid(*cursor)) { + return VTreeError.INVALID_REFERENCE?; + } + + // same for the parent, if it's invalid it can't have children + if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { + return VTreeError.INVALID_REFERENCE?; + } + + // find the first child, update the cursor and return the ref + for (isz 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] + */ +fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) +{ + if (cursor == null) { + return VTreeError.INVALID_ARGUMENT?; + } + + isz[] 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; + queue[..] = -1; + + // iterate through the queue appending found children + isz pos, off; + do { + // printf ("ref=%d\n", ref); + for (isz 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 (tree.ref_is_valid(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 (tree.ref_is_valid(*cursor)) { + return queue[(*cursor)++]; + } + + return -1; +} + +fn isz! VTree.parentof(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return VTreeError.REFERENCE_NOT_PRESENT?; + } + + return tree.refs[ref]; +} + +fn ElemType! VTree.get(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return VTreeError.REFERENCE_NOT_PRESENT?; + } + + return tree.vector[ref]; +} + +fn void VTree.print(&tree) +{ + for (isz i = 0; i < tree.size(); i++) { + if (tree.refs[i] == -1) { + continue; + } + io::printf("[%d] {parent=%d, data=", i, tree.refs[i]); + io::print(tree.vector[i]); + io::printn("}"); + } +} diff --git a/stuff/generic_hash.h b/stuff/generic_hash.h deleted file mode 100644 index 1aa7601..0000000 --- a/stuff/generic_hash.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef _HASH_GENERIC -#define _HASH_GENERIC - -#include -#include -#include - - -// 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}/ -#define HASH_RATIO32 ((uint64_t)2654435769u) -#define HASH_RATIO64 ((uint64_t)11400714819322457583u) -// salt for string hashing -#define HASH_SALT ((uint64_t)0xbabb0cac) - - -/* Ready-made compares */ -static inline int hash_cmp_u32(uint32_t a, uint32_t b) { return a == b; } -static inline int hash_cmp_u64(uint64_t a, uint64_t b) { return a == b; } -static inline int hash_cmp_str(const char *a, const char *b) { return strcmp(a, b) == 0; } - - -/* Ready-made hashes */ -static inline uint32_t hash_u64(uint64_t c) -{ - return (uint64_t)((((uint64_t)c+HASH_SALT)*HASH_RATIO64)>>32); -} -static inline uint32_t hash_u32(uint32_t c) -{ - return (uint32_t)((((uint64_t)c<<31)*HASH_RATIO64)>>32); -} -static inline uint32_t hash_str(const char *s) -{ - uint32_t h = HASH_SALT; - const uint8_t *v = (const uint8_t *)(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; -} - - -#define HASH_DECL(htname, codetype, datatype, hashfn, cmpfn) \ -struct htname##_entry { \ - codetype code; \ - datatype data; \ -}; \ -\ -struct htname##_ref { \ - uint32_t items, size, exp; \ - struct htname##_entry bucket[]; \ -}; \ -\ -\ -struct htname##_ref * htname##_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 */ \ - struct htname##_ref *ht = malloc(sizeof(struct htname##_ref)+sizeof(struct htname##_entry)*size); \ - if (ht) { \ - ht->items = 0; \ - ht->size = size; \ - ht->exp = exp; \ - memset(ht->bucket, 0, sizeof(struct htname##_entry)*size); \ - } \ - return ht; \ -} \ -\ -\ -void htname##_destroy(struct htname##_ref *ht) \ -{ \ - if (ht) free(ht); \ -} \ -\ -\ -static uint32_t htname##_lookup(struct htname##_ref *ht, uint32_t hash, uint32_t idx) \ -{ \ - if (!ht) return 0; \ - uint32_t mask = ht->size-1; \ - uint32_t step = (hash >> (32 - ht->exp)) | 1; \ - return (idx + step) & mask; \ -} \ -\ -\ -/* Find and return the element by code */ \ -struct htname##_entry * htname##_search(struct htname##_ref *ht, codetype code)\ -{ \ - if (!ht) return NULL; \ - uint32_t h = hashfn(code); \ - for (uint32_t i=h, x=0; ; x++) { \ - i = htname##_lookup(ht, h, i); \ - if (x > (ht->size<<1) || \ - !ht->bucket[i].code || \ - cmpfn(ht->bucket[i].code, code) \ - ) { \ - return &(ht->bucket[i]); \ - } \ - } \ - return NULL; \ -} \ -\ -\ -/* FIXME: this simply overrides the found item */ \ -struct htname##_entry * htname##_insert(struct htname##_ref *ht, struct htname##_entry *entry) \ -{ \ - struct htname##_entry *r = htname##_search(ht, entry->code); \ - if (r) { \ - if (!r->code) \ - ht->items++; \ - *r = *entry; \ - } \ - return r; \ -} \ -\ -\ -struct htname##_entry * htname##_remove(struct htname##_ref *ht, codetype code)\ -{ \ - if (!ht) return NULL; \ - struct htname##_entry *r = htname##_search(ht, code); \ - if (r) r->code = 0; \ - return r; \ -} \ - -#endif diff --git a/stuff/generic_stack.h b/stuff/generic_stack.h deleted file mode 100644 index b3d5f1e..0000000 --- a/stuff/generic_stack.h +++ /dev/null @@ -1,118 +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 -#if STACK_DISABLE_HASH -#define STACK_HASH(p, s, h) {} -#else -#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; \ -} -#endif - - -// TODO: add a rolling hash -#define STACK_DECL(stackname, type) \ -struct stackname { \ - type *items; \ - int size, idx, old_idx; \ - unsigned int hash, old_hash; \ -}; \ -\ -\ -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 diff --git a/stuff/main.c b/stuff/main.c deleted file mode 100755 index 764be50..0000000 --- a/stuff/main.c +++ /dev/null @@ -1,214 +0,0 @@ -//#!/usr/bin/tcc -run - -#include -#include -#include - - -#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; -} diff --git a/stuff/vectree.h b/stuff/vectree.h deleted file mode 100644 index 949bee1..0000000 --- a/stuff/vectree.h +++ /dev/null @@ -1,348 +0,0 @@ -#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 - diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/test_bitsruct.c3 b/test/test_bitsruct.c3 new file mode 100644 index 0000000..06d1722 --- /dev/null +++ b/test/test_bitsruct.c3 @@ -0,0 +1,14 @@ +bitstruct Bits : uint { + bool a : 0; + bool b : 1; + bool c : 2; +} + +fn int main() +{ + Bits a = {false, true, false}; + Bits b = {true, true, false}; + Bits c = a | b; + + return 0; +} diff --git a/test/test_union.c3 b/test/test_union.c3 new file mode 100644 index 0000000..df09af1 --- /dev/null +++ b/test/test_union.c3 @@ -0,0 +1,26 @@ + +struct CmdA { + int a, b; +} + +struct CmdB { + float a, b; +} + +union AnyCmd { + CmdA a; + CmdB b; +} + +struct Cmd { + int type; + AnyCmd cmd; +} + +fn int main() +{ + Cmd c; + c.type = 1; + c.cmd.a = {.a = 1, .b = 2}; + return 0; +} diff --git a/test/test_vtree.c3 b/test/test_vtree.c3 new file mode 100644 index 0000000..187ce91 --- /dev/null +++ b/test/test_vtree.c3 @@ -0,0 +1,7 @@ +import std::io; +import vtree; + +fn int main() +{ + return 0; +} diff --git a/timer.c b/timer.c deleted file mode 100644 index 03b7a4c..0000000 --- a/timer.c +++ /dev/null @@ -1,69 +0,0 @@ -#define _POSIX_C_SOURCE 200809l - -#include - -#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); -} diff --git a/timer.h b/timer.h deleted file mode 100644 index 94e546b..0000000 --- a/timer.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef UG_TIMER_H_ -#define UG_TIMER_H_ - -#include - -#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 diff --git a/ugui.c b/ugui.c deleted file mode 100644 index 7f08025..0000000 --- a/ugui.c +++ /dev/null @@ -1,1061 +0,0 @@ -#define _DEFAULT_SOURCE - -#include -#include -#include -#include - -#include "raylib.h" -#include "ugui.h" -#include "timer.h" - -#define RGBA(u) \ - (UgColor) \ - { \ - .r = (u >> 24) & 0xff, .g = (u >> 16) & 0xff, .b = (u >> 8) & 0xff, \ - .a = (u >> 0) & 0xff \ - } -#define DIV_FILL \ - (UgRect) { .x = 0, .y = 0, .w = 0, .h = 0 } - -#define MARK() \ - do { \ - printf("================== lmao ==================\n"); \ - } while (0) - -#define BIT(x) (1 << (x)) -#define FTEST(e, f) ((e)->flags & (f)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define ABS(a) ((a) < 0 ? -(a) : (a)) -#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) - -#define STACK_STEP 10 -#define MAX_ELEMS 128 -#define MAX_CMDS 256 -#define ROOT_ID 1 - -typedef struct _UgCmd { - enum { - CMD_RECT = 0, - } type; - - union { - struct { - UgRect rect; - UgColor color; - } rect; - }; -} UgCmd; - -typedef struct _UgFifo { - int size, in, out, count; - UgCmd *vec; -} UgFifo; - -int ug_fifo_init(UgFifo *fifo, uint32_t size); -int ug_fifo_free(UgFifo *fifo); -int ug_fifo_enqueue(UgFifo *fifo, UgCmd *cmd); -int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd); - -int ug_fifo_init(UgFifo *fifo, uint32_t size) -{ - if (fifo == NULL) { - return -1; - } - - fifo->size = size; - fifo->in = 0; - fifo->out = 0; - fifo->count = 0; - fifo->vec = calloc(size, sizeof(UgCmd)); - - if (fifo->vec == NULL) { - return -1; - } - - return 0; -} - -int ug_fifo_free(UgFifo *fifo) -{ - if (fifo != NULL && fifo->vec != NULL) { - free(fifo->vec); - fifo->size = 0; - fifo->in = 0; - fifo->out = 0; - fifo->count = 0; - } - return 0; -} - -#define FIFO_STEP 10 - -int ug_fifo_enqueue(UgFifo *fifo, UgCmd *cmd) -{ - if (fifo == NULL || cmd == NULL) { - return -1; - } - - if (fifo->count >= fifo->size) { - // UgCmd *tmp = reallocarray(fifo->vec, fifo->size + - // fifo_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; - // } - // fifo->vec = tmp; - // fifo->size += fifo_STEP; - return -1; - } - - fifo->vec[fifo->in] = *cmd; - - fifo->in = (fifo->in + 1) % fifo->size; - fifo->count++; - - return 0; -} - -int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd) -{ - if (fifo == NULL || cmd == NULL) { - return -1; - } - - if (fifo->count <= 0) { - // UgCmd *tmp = reallocarray(fifo->vec, fifo->size + - // fifo_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; - // } - // fifo->vec = tmp; - // fifo->size += fifo_STEP; - return -1; - } - - *cmd = fifo->vec[fifo->out]; - - fifo->out = (fifo->out + 1) % fifo->size; - fifo->count--; - - return 0; -} - -enum InputEventFlags { - INPUT_CTX_SIZE = 1 << 0, // window size was changed - INPUT_CTX_FOCUS = 1 << 1, // window focus changed - INPUT_CTX_MOUSEMOVE = 1 << 2, // mouse was moved - INPUT_CTX_MOUSEBTN = 1 << 3, // mouse button pressed or released -}; - -struct _UgCtx { - enum { - row = 0, - column, - floating, - } layout; - - UgTree tree; - UgElemCache cache; - UgFifo fifo; - - struct { - int width, height; - } size; - - struct { // css box model - UgRect padding; - UgRect border; - UgRect margin; - UgColor bgcolor; // background color - UgColor fgcolor; // foreground color - UgColor bcolor; // border color - } style; - - // input structure, it describes the events received between frames - struct { - // flags: lists which input events have been received - uint32_t flags; - int has_focus; - UgPoint mouse_pos, mouse_delta; - // mouse_down: bitmap of mouse buttons that are held - // mouse_updated: bitmap of mouse buttons that have been updated - // mouse_released = mouse_updated & ~mouse_down - // mouse_pressed = mouse_updated & mouse_down - uint32_t mouse_down; - uint32_t mouse_updated; - } input; - - int div_using; // tree node indicating the current active div -}; - -// layouting -int ug_layout_set_row(UgCtx *ctx); -int ug_layout_set_column(UgCtx *ctx); -int ug_layout_set_floating(UgCtx *ctx); -// these reset the offsets introduced by the previous elements -int ug_layout_next_row(UgCtx *ctx); -int ug_layout_next_column(UgCtx *ctx); - -int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); -int ug_div_end(UgCtx *ctx); -int ug_input_window_size(UgCtx *ctx, int width, int height); - -// static UgId djb2(const char *str); -static UgId FNV_1a(const char *str); - -int ug_button(UgCtx *ctx, const char *label, UgRect size); - -static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style); - -// search the element of the corresponding id in the cache, if no element is found -// insert a new one of that id. Return the pointer to the element -static int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) -{ - int is_new = 0; - uint32_t cache_idx; - - UgElem *c_elem = ug_cache_search(&ctx->cache, id); - if (c_elem == NULL) { - UgElem tmp = {.id = id}; - c_elem = ug_cache_insert_new(&ctx->cache, &tmp, &cache_idx); - is_new = 1; - } - - *elem = c_elem; - return is_new; -} - -static int is_point_in_rect(UgPoint p, UgRect r) -{ - if ((p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h)) { - return 1; - } - return 0; -} - -static int get_parent(UgCtx *ctx, UgElem **parent) -{ - // take a reference to the parent - // FIXME: if the tree held pointers to the elements then no more - // redundant cache search - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - return 0; -} - -int ug_init(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - *ctx = (UgCtx) {0}; - - ug_tree_init(&ctx->tree, MAX_ELEMS); - ug_fifo_init(&ctx->fifo, MAX_CMDS); - - ctx->cache = ug_cache_init(); - ctx->layout = row; - ctx->div_using = 0; - - // TODO: add style config - ctx->style.margin = (UgRect) {1, 1, 1, 1}; - ctx->style.padding = (UgRect) {0}; - ctx->style.border = (UgRect) {0}; - - ctx->style.bgcolor = (UgColor) {0}; - ctx->style.fgcolor = (UgColor) {0}; - ctx->style.bcolor = (UgColor) {0}; - - return 0; -} - -int ug_destroy(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - ug_tree_destroy(&ctx->tree); - ug_cache_free(&ctx->cache); - ug_fifo_free(&ctx->fifo); - - return 0; -} - -static void print_tree(UgCtx *ctx) -{ - printf("ctx->tree: ["); - for (int c = -1, x; (x = ug_tree_level_order_it(&ctx->tree, 0, &c)) != -1;) { - printf( - "[%d:%d,%.4lx], ", - x, - ug_tree_parentof(&ctx->tree, x), - ug_tree_get(&ctx->tree, x) & 0xffff - ); - } - printf("[-1]]\n"); -} - -int ug_frame_begin(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - // 1. Create the root div element - UgRect space = { - .x = 0, - .y = 0, - .w = ctx->size.width, - .h = ctx->size.height, - }; - UgElem root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .flags = 0, - .event = 0, - .rect = space, - .div = - { - .layout = DIV_LAYOUT_ROW, - .origin_r = {0}, - .origin_c = {0}, - .color_bg = {0}, - }, - }; - - // The root should have the updated flag only if the size of the window - // was changed between frames, this propagates an element size recalculation - // down the element tree - if (ctx->input.flags & INPUT_CTX_SIZE) { - root.flags |= ELEM_UPDATED; - } - - // if the window has focus then the root element also has focus, no other - // computation needed, child elements need to check the mouse positon and - // other stuff - if (ctx->input.has_focus) { - root.flags |= ELEM_HASFOCUS; - } - - // FIXME: check errors - // 2. Get the root element from the cache and update it - UgElem *c_elem; - int is_new = search_or_insert(ctx, &c_elem, root.id); - - // flags always need to be set to the new flags - c_elem->flags = root.flags; - - if (is_new || FTEST(&root, ELEM_UPDATED)) { - *c_elem = root; - } - - // 3. Push the root element into the element tree - ctx->div_using = ug_tree_add(&ctx->tree, root.id, 0); - - if (ctx->div_using < 0) { - printf("why?\n"); - return -1; - } - - // print_tree(ctx); - - // The root element does not push anything to the stack - // TODO: add a background color taken from a theme or config - - printf("##### Frame Begin #####\n"); - - return 0; -} - -int ug_frame_end(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - // 1. clear the tree - ug_tree_prune(&ctx->tree, 0); - - // 2. clear input fields - ctx->input.flags = 0; - -// draw mouse position -#if 1 - UgCmd cmd = { - .type = CMD_RECT, - .rect = - { - .rect = - { - .x = ctx->input.mouse_pos.x - 2, - .y = ctx->input.mouse_pos.y - 2, - .w = 4, - .h = 4, - }, - .color = RGBA(0xff00ffff), - }, - }; - ug_fifo_enqueue(&(ctx->fifo), &cmd); -#endif - - printf("##### Frame End #####\n\n"); - return 0; -} - -// Window size was changed -int ug_input_window_size(UgCtx *ctx, int width, int height) -{ - if (ctx == NULL) { - return -1; - } - - if (width <= 0 || height <= 0) { - return -1; - } - - if (width >= 0) { - ctx->size.width = width; - } - if (height >= 0) { - ctx->size.height = height; - } - - ctx->input.flags |= INPUT_CTX_SIZE; - - return 0; -} - -// Window gained/lost focus -int ug_input_changefoucs(UgCtx *ctx, int has_focus) -{ - if (ctx == NULL) { - return -1; - } - - // FIXME: raylib only has an API to query the focus status so we have to - // update the input flag only if the focus changed - if (ctx->input.has_focus != (!!has_focus)) { - ctx->input.flags |= INPUT_CTX_FOCUS; - } - ctx->input.has_focus = !!has_focus; - - return 0; -} - -enum ug_mouse_buttons { - 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), -}; - -const uint32_t mouse_supported_mask = - UG_BTN_LEFT | UG_BTN_MIDDLE | UG_BTN_RIGHT | UG_BTN_4 | UG_BTN_5; - -// Mouse Button moved -int ug_input_mouse_button(UgCtx *ctx, uint32_t button_mask) -{ - if (ctx == NULL) { - return -1; - } - - if (button_mask & ~mouse_supported_mask) { - return -1; - } - - ctx->input.mouse_updated = ctx->input.mouse_down ^ button_mask; - ctx->input.mouse_down = button_mask; - ctx->input.flags |= INPUT_CTX_MOUSEBTN; - - printf( - "Mouse Down: %s%s%s%s%s\n", - button_mask & UG_BTN_LEFT ? "BTN_LEFT " : "", - button_mask & UG_BTN_RIGHT ? "BTN_RIGHT " : "", - button_mask & UG_BTN_MIDDLE ? "BTN_MIDDLE " : "", - button_mask & UG_BTN_4 ? "BTN_4 " : "", - button_mask & UG_BTN_5 ? "BTN_5 " : "" - ); - return 0; -} - -// Mouse was moved, report absolute position -int ug_input_mouse_abs(UgCtx *ctx, int x, int y) { return 1; } - -// Mouse was moved, report relative motion -int ug_input_mouse_delta(UgCtx *ctx, int dx, int dy) -{ - if (ctx == NULL) { - return -1; - } - - ctx->input.mouse_delta.x = dx; - ctx->input.mouse_delta.y = dy; - - int mx, my; - mx = ctx->input.mouse_pos.x + dx; - my = ctx->input.mouse_pos.y + dy; - - ctx->input.mouse_pos.x = CLAMP(mx, 0, ctx->size.width); - ctx->input.mouse_pos.y = CLAMP(my, 0, ctx->size.height); - - ctx->input.flags |= INPUT_CTX_MOUSEMOVE; - - return 0; -} - -static UgId FNV_1a(const char *str) -{ - const uint64_t fnv_off = 0xcbf29ce484222325; - const uint64_t fnv_prime = 0x100000001b3; - - uint64_t hash = fnv_off; - for (uint32_t c; (c = str[0]) != 0; str++) { - hash ^= c; - hash *= fnv_prime; - } - - return hash; -} - -#if 0 -static UgId djb2(const char *str) -{ - uint64_t hash = 5381; - uint32_t c; - - while ((c = (str++)[0]) != 0) { - hash = ((hash << 5) + hash) + c; - } /* hash * 33 + c */ - - return hash; -} -#endif - -int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) -{ - if (ctx == NULL || label == NULL) { - return -1; - } - - UgId id = FNV_1a(label); - - UgElem *c_elem; - int is_new = search_or_insert(ctx, &c_elem, id); - - // FIXME: why save the id in the tree and not something more direct like - // the element pointer or the index into the cache vector? - int div_node = ug_tree_add(&ctx->tree, id, ctx->div_using); - if (div_node < 0) { - // do something - printf("Error adding to tree\n"); - } - - UgElem *parent; - if (get_parent(ctx, &parent)) { - return -1; - } - - print_tree(ctx); - - // Use the current div - ctx->div_using = div_node; - - // 1. Fill the element fields - // this resets the flags - c_elem->type = ETYPE_DIV; - c_elem->flags = 0; - - // do layout and update flags only if the element was updated - if (is_new || FTEST(parent, ELEM_UPDATED)) { - // 2. layout the element - c_elem->rect = position_element(ctx, parent, div, 0); - - // 3. Mark the element as updated - c_elem->flags |= ELEM_UPDATED; - - // 4. Fill the div fields - c_elem->div.layout = parent->div.layout; - c_elem->div.origin_c = (UgPoint) { - .x = c_elem->rect.x, - .y = c_elem->rect.y, - }; - c_elem->div.color_bg = RGBA(0xff0000ff); - c_elem->div.origin_r = c_elem->div.origin_c; - } else if (FTEST(parent, ELEM_HASFOCUS)) { - if (is_point_in_rect(ctx->input.mouse_pos, c_elem->rect)) { - c_elem->flags |= ELEM_HASFOCUS; - } - } else { - // TODO: check active - // TODO: check scrollbars - // TODO: check resizeable - } - - // Add the background to the draw stack - UgCmd cmd = { - .type = CMD_RECT, - .rect = - { - .rect = c_elem->rect, - .color = c_elem->div.color_bg, - }, - }; - ug_fifo_enqueue(&ctx->fifo, &cmd); - - return 0; -} - -int ug_div_end(UgCtx *ctx) -{ - // the div_using returns to the parent of the current one - ctx->div_using = ug_tree_parentof(&ctx->tree, ctx->div_using); - return 0; -} - -// position the rectangle inside the parent according to the layout -static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) -{ - UgRect elem_rect = {0}; - UgPoint origin = {0}; - - // 1. Select the right origin - switch (parent->div.layout) { - case DIV_LAYOUT_ROW: - origin = parent->div.origin_r; - break; - case DIV_LAYOUT_COLUMN: - origin = parent->div.origin_c; - break; - case DIV_LAYOUT_FLOATING: // none - default: - // Error - break; - } - - // 2. Position the rect - elem_rect.x = origin.x + rect.x; - elem_rect.y = origin.y + rect.y; - - // 3. Calculate width & height - // TODO: what about negative values? - // FIXME: account for origin offset!! - elem_rect.w = rect.w > 0 ? rect.w : parent->rect.w; - elem_rect.h = rect.h > 0 ? rect.h : parent->rect.h; - - // 4. Update the origins of the parent - parent->div.origin_r = (UgPoint) { - .x = elem_rect.x + elem_rect.w, - .y = elem_rect.y, - }; - parent->div.origin_c = (UgPoint) { - .x = elem_rect.x, - .y = elem_rect.y + elem_rect.h, - }; - - // if using the style then apply margins - // FIXME: this does not work - if (style && parent->div.layout != DIV_LAYOUT_FLOATING) { - elem_rect.x += ctx->style.margin.x; - elem_rect.y += ctx->style.margin.y; - - // total keep-out borders - UgRect margin_tot = { - .x = ctx->style.padding.x + ctx->style.border.x + - ctx->style.margin.x, - .y = ctx->style.padding.y + ctx->style.border.y + - ctx->style.margin.y, - .w = ctx->style.padding.w + ctx->style.border.x + - ctx->style.margin.w, - .h = ctx->style.padding.h + ctx->style.border.x + - ctx->style.margin.h, - }; - - parent->div.origin_r.x += margin_tot.x + margin_tot.w; - // parent->div.origin_r.y += margin_tot.h; - - // parent->div.origin_c.x += margin_tot.w; - parent->div.origin_c.y += margin_tot.y + margin_tot.h; - } - - /* - printf( - "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) -> {%d %d %d - %d}\n", parent->id, rect.x, rect.y, rect.w, rect.h, parent->rect.x, - parent->rect.y, - parent->rect.w, - parent->rect.h, - elem_rect.x, - elem_rect.y, - elem_rect.w, - elem_rect.h - ); - */ - return elem_rect; -} - -int is_hovered(UgCtx *ctx, UgElem *elem) -{ - if (ctx == NULL || elem == NULL) { - return 0; - } - return is_point_in_rect(ctx->input.mouse_pos, elem->rect); -} - -int ug_button(UgCtx *ctx, const char *label, UgRect size) -{ - if (ctx == NULL || label == NULL) { - return -1; - } - - UgId id = FNV_1a(label); - - // TODO: do layouting if the element is new or the parent has updated - UgElem *c_elem; - int is_new = search_or_insert(ctx, &c_elem, id); - - // add it to the tree - ug_tree_add(&ctx->tree, id, ctx->div_using); - print_tree(ctx); - - UgElem *parent; - if (get_parent(ctx, &parent)) { - return -1; - } - - // 1. Fill the element fields - // this resets the flags - c_elem->type = ETYPE_BUTTON; - c_elem->flags = 0; - UgColor bg_color = RGBA(0x0000ffff); - - // if the element is new or the parent was updated then redo layout - if (is_new || FTEST(parent, ELEM_UPDATED)) { - // 2. Layout - c_elem->rect = position_element(ctx, parent, size, 1); - - // TODO: 3. Fill the button specific fields - } - - // TODO: Check for interactions - if (FTEST(parent, ELEM_HASFOCUS)) { - if (is_hovered(ctx, c_elem)) { - c_elem->flags |= ELEM_HASFOCUS; - c_elem->event |= EVENT_MOUSE_HOVER; - bg_color = RGBA(0x00ff00ff); - - if (ctx->input.mouse_down & UG_BTN_LEFT) { - c_elem->event |= EVENT_MOUSE_HOLD; - } else { - c_elem->event &= ~EVENT_MOUSE_HOLD; - } - } else { - c_elem->event &= ~EVENT_MOUSE_HOVER; - } - } - - // Draw the button - UgCmd cmd = { - .type = CMD_RECT, - .rect = - { - .rect = c_elem->rect, - .color = bg_color, - }, - }; - ug_fifo_enqueue(&ctx->fifo, &cmd); - - return c_elem->event; -} - -int ug_layout_set_row(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.layout = DIV_LAYOUT_ROW; - - return 0; -} - -int ug_layout_set_column(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.layout = DIV_LAYOUT_COLUMN; - - return 0; -} - -int ug_layout_set_floating(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.layout = DIV_LAYOUT_FLOATING; - - return 0; -} - -int ug_layout_next_row(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.origin_r = (UgPoint) { - .x = parent->rect.x, - .y = parent->div.origin_c.y, - }; - - parent->div.origin_c = parent->div.origin_r; - - return 0; -} - -int ug_layout_next_column(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.origin_c = (UgPoint) { - .x = parent->div.origin_r.x, - .y = parent->rect.y, - }; - - parent->div.origin_r = parent->div.origin_c; - - return 0; -} - -#if 1 -int main(void) -{ - UgCtx ctx; - ug_init(&ctx); - - int width = 800, height = 450; - SetConfigFlags(FLAG_WINDOW_RESIZABLE); - InitWindow(width, height, "Ugui Test"); - ug_input_window_size(&ctx, width, height); - - double median_times[10][4] = {0}; - size_t frame = 0; - double median_input, median_layout, median_draw, median_tot; - - // Main loop - while (!WindowShouldClose()) { - const int PARTIAL_INPUT = 0; - const int PARTIAL_LAYOUT = 1; - const int PARTIAL_DRAW = 2; - timer_start(); - - /*** Start Input Handling ***/ - if (IsWindowResized()) { - width = GetScreenWidth(); - height = GetScreenHeight(); - ug_input_window_size(&ctx, width, height); - } - - ug_input_changefoucs(&ctx, IsWindowFocused()); - - // FIXME: In raylib it doesn't seem to be a quick way to check if - // a mouse input event was received, so for now just use - // the delta information - Vector2 mousedelta = GetMouseDelta(); - if (mousedelta.x || mousedelta.y) { - ug_input_mouse_delta(&ctx, mousedelta.x, mousedelta.y); - } - - uint32_t mask = 0; - mask |= IsMouseButtonDown(MOUSE_BUTTON_LEFT) ? UG_BTN_LEFT : 0; - mask |= IsMouseButtonDown(MOUSE_BUTTON_RIGHT) ? UG_BTN_RIGHT : 0; - mask |= IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) ? UG_BTN_MIDDLE : 0; - ug_input_mouse_button(&ctx, mask); - - timer_partial(PARTIAL_INPUT); - /*** End Input Handling ***/ - - /*** Start UI Handling ***/ - ug_frame_begin(&ctx); - - // main div, fill the whole window - ug_div_begin(&ctx, "main", DIV_FILL); - { - ug_layout_set_column(&ctx); - if (ug_button( - &ctx, - "button0", - (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} - ) & - EVENT_MOUSE_HOLD) { - printf("HOLDING button0\n"); - } - ug_layout_next_column(&ctx); - ug_button(&ctx, "button1", (UgRect) {.w = 30, .h = 30}); - ug_layout_next_column(&ctx); - ug_button(&ctx, "button2", (UgRect) {.w = 30, .h = 30}); - } - ug_div_end(&ctx); - - ug_frame_end(&ctx); - timer_partial(PARTIAL_LAYOUT); - /*** End UI Handling ***/ - - /*** Start UI Drawing ***/ - BeginDrawing(); - // ClearBackground(BLACK); - - printf("----- Draw Begin -----\n"); - Color c; - for (UgCmd cmd; ug_fifo_dequeue(&ctx.fifo, &cmd) >= 0;) { - switch (cmd.type) { - case CMD_RECT: - printf( - "draw rect x=%d y=%d w=%d h=%d\n", - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h - ); - c = (Color) { - .r = cmd.rect.color.r, - .g = cmd.rect.color.g, - .b = cmd.rect.color.b, - .a = cmd.rect.color.a, - }; - DrawRectangle( - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h, - c - ); - break; - default: - printf("Unknown cmd type: %d\n", cmd.type); - break; - } - } - printf("----- Draw End -----\n\n"); - - EndDrawing(); - timer_partial(PARTIAL_DRAW); - timer_stop(); - /*** End UI Drawing ***/ - - median_times[frame][PARTIAL_INPUT] = - 1e3 * timer_get_sec(PARTIAL_INPUT); - median_times[frame][PARTIAL_LAYOUT] = - 1e3 * timer_get_sec(PARTIAL_LAYOUT); - median_times[frame][PARTIAL_DRAW] = - 1e3 * timer_get_sec(PARTIAL_DRAW); - median_times[frame][3] = 1e3 * timer_get_sec(-1); - - frame += 1; - frame %= 10; - - if (frame == 0) { - median_input = 0; - median_layout = 0; - median_draw = 0; - median_tot = 0; - for (size_t i = 0; i < 10; i++) { - median_input += median_times[i][PARTIAL_INPUT]; - median_layout += median_times[i][PARTIAL_LAYOUT]; - median_draw += median_times[i][PARTIAL_DRAW]; - median_tot += median_times[i][3]; - } - median_input /= 10; - median_layout /= 10; - median_draw /= 10; - median_tot /= 10; - } - - printf("input time: %lfms\n", median_input); - printf("layout time: %lfms\n", median_layout); - printf("draw time: %lfms\n", median_draw); - printf("total time: %lfms\n", median_tot); - - // Throttle Frames - // TODO: add an fps limit, time frame generation and log it - const float TARGET_FPS = 100; - float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); - WaitTime(wait_time); - } - - CloseWindow(); - - ug_destroy(&ctx); - return 0; -} -#endif diff --git a/ugui.h b/ugui.h deleted file mode 100644 index c2ae79f..0000000 --- a/ugui.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef _UGUI_H -#define _UGUI_H - -#include - -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 uint64_t UgId; - -typedef enum { - ETYPE_NONE = 0, - ETYPE_DIV, - ETYPE_BUTTON, -} UgElemType; - -enum UgElemFlags { - ELEM_UPDATED = 1 << 0, - ELEM_HASFOCUS = 1 << 1, -}; - -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, -}; - -typedef struct { - UgId id; - uint32_t flags; - uint32_t event; - UgRect rect; - UgElemType type; - - // 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 - -typedef struct { - int size, elements; - UgId *vector; // vector of element ids - int *refs, *ordered_refs; -} UgTree; - -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 diff --git a/vectree.c b/vectree.c deleted file mode 100644 index ab58cd5..0000000 --- a/vectree.c +++ /dev/null @@ -1,355 +0,0 @@ -#include -#include - -#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]; -}