From 3a8a55d17751a6bfb4b195ec700234da8140d143 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 10 Mar 2024 17:26:03 +0100 Subject: [PATCH] checkpoint --- Makefile | 4 +- fm/libconf.c | 193 +++++++++++++++++++++++++++++++++ fm/libconf.h | 7 ++ timer.c | 69 ++++++++++++ timer.h | 16 +++ ugui.c | 301 ++++++++++++++++++++++++++++++++++----------------- ugui.h | 3 +- 7 files changed, 493 insertions(+), 100 deletions(-) create mode 100644 fm/libconf.c create mode 100644 fm/libconf.h create mode 100644 timer.c create mode 100644 timer.h diff --git a/Makefile b/Makefile index 31a689a..0d82e80 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,12 @@ LDFLAGS = -Lraylib/lib -lm all: ugui -ugui: ugui.o vectree.o cache.o raylib/lib/libraylib.a +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/fm/libconf.c b/fm/libconf.c new file mode 100644 index 0000000..07ac86e --- /dev/null +++ b/fm/libconf.c @@ -0,0 +1,193 @@ +#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 new file mode 100644 index 0000000..f92c29b --- /dev/null +++ b/fm/libconf.h @@ -0,0 +1,7 @@ +#ifndef LIBCONF_H_ +#define LIBCONF_H_ + +const char *lcnf_get_conf_file(const char *id, const char *name, int global); +const char *lcnf_get_conf_dir(const char *id, int global); + +#endif diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..03b7a4c --- /dev/null +++ b/timer.c @@ -0,0 +1,69 @@ +#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 new file mode 100644 index 0000000..94e546b --- /dev/null +++ b/timer.h @@ -0,0 +1,16 @@ +#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 index 10771de..fb7cd88 100644 --- a/ugui.c +++ b/ugui.c @@ -7,6 +7,7 @@ #include "raylib.h" #include "ugui.h" +#include "timer.h" #define RGBA(u) \ (UgColor) \ @@ -22,7 +23,11 @@ printf("lmao\n"); \ } while (0) -#define FTEST(e, f) ((e)->flags & (f)) +#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 @@ -132,7 +137,10 @@ int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd) } enum InputEventFlags { - INPUT_CTX_SIZE = 1 << 0, // window size was changed + 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 { @@ -161,7 +169,16 @@ struct _UgCtx { // input structure, it describes the events received between frames struct { + // flags: lists which input events have been received uint32_t flags; + int has_focus; + int mouse_x, mouse_y, mdelta_x, mdelta_y; + // 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 @@ -175,104 +192,16 @@ int ug_layout_set_floating(UgCtx *ctx); 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); +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); -UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style); - -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); - - // Main loop - while (!WindowShouldClose()) { - - // Input handling - if (IsWindowResized()) { - width = GetScreenWidth(); - height = GetScreenHeight(); - ug_input_window_size(&ctx, width, height); - } - - // UI handling - ug_frame_begin(&ctx); - - // main div, fill the whole window - ug_div_begin(&ctx, "main", DIV_FILL); - { - ug_layout_set_column(&ctx); - ug_button( - &ctx, - "button0", - (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} - ); - 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); - - // 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(); - - WaitTime(0.2); - } - - CloseWindow(); - - ug_destroy(&ctx); - return 0; -} +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 @@ -385,6 +314,7 @@ int ug_frame_begin(UgCtx *ctx) .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 @@ -392,13 +322,22 @@ int ug_frame_begin(UgCtx *ctx) 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); 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) { @@ -432,6 +371,7 @@ int ug_frame_end(UgCtx *ctx) return 0; } +// Window size was changed int ug_input_window_size(UgCtx *ctx, int width, int height) { if (ctx == NULL) { @@ -452,6 +392,48 @@ int ug_input_window_size(UgCtx *ctx, int width, int height) 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; +} + +// 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.mdelta_x = dx; + ctx->input.mdelta_y = dy; + + int mx, my; + mx = ctx->input.mouse_x + dx; + my = ctx->input.mouse_y + dy; + + ctx->input.mouse_x = CLAMP(mx, 0, ctx->size.width); + ctx->input.mouse_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; @@ -466,6 +448,7 @@ static UgId FNV_1a(const char *str) return hash; } +#if 0 static UgId djb2(const char *str) { uint64_t hash = 5381; @@ -477,6 +460,7 @@ static UgId djb2(const char *str) return hash; } +#endif int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) { @@ -556,7 +540,7 @@ int ug_div_end(UgCtx *ctx) } // position the rectangle inside the parent according to the layout -UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) +static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) { UgRect elem_rect = {0}; UgPoint origin = {0}; @@ -663,7 +647,7 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) c_elem->flags = 0; // if the element is new or the parent was updated then redo layout - if (is_new || parent->flags & ELEM_UPDATED) { + if (is_new || FTEST(parent, ELEM_UPDATED)) { // 2. Layout c_elem->rect = position_element(ctx, parent, size, 1); @@ -810,3 +794,124 @@ int ug_layout_next_column(UgCtx *ctx) 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); + + // 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); + } + 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); + ug_button( + &ctx, + "button0", + (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} + ); + 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 ***/ + + printf("input time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_INPUT)); + printf("layout time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_LAYOUT)); + printf("draw time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_DRAW)); + printf("total time: %lfms\n", 1e3 * timer_get_sec(-1)); + + // Throttle Frames + // TODO: add an fps limit, time frame generation and log it + const float TARGET_FPS = 10; + 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 index 4d91d68..05819fd 100644 --- a/ugui.h +++ b/ugui.h @@ -24,7 +24,8 @@ typedef enum { } UgElemType; enum UgElemFlags { - ELEM_UPDATED = 1 << 0, + ELEM_UPDATED = 1 << 0, + ELEM_HASFOCUS = 1 << 1, }; typedef struct {