inital nodes and props
This commit is contained in:
1 parent
98f798ad45
commit
5dad34f2e3
12 files changed
+1101
-3
No files matched your search
@@ -7,3 +7,4 @@ build-wasm/
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
/.emscripten-cache
|
/.emscripten-cache
|
||||||
/.cache/clangd/index
|
/.cache/clangd/index
|
||||||
|
/build-asan
|
||||||
@@ -5,11 +5,19 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|||||||
|
|
||||||
option(REGOLITH_BUILD_TESTS "Build tests" ON)
|
option(REGOLITH_BUILD_TESTS "Build tests" ON)
|
||||||
option(REGOLITH_BUILD_SDL3 "Build the SDL3 backend" ON)
|
option(REGOLITH_BUILD_SDL3 "Build the SDL3 backend" ON)
|
||||||
|
option(REGOLITH_SANITIZE "Build with ASan and UBSan" OFF)
|
||||||
|
if(REGOLITH_SANITIZE AND NOT MSVC AND NOT EMSCRIPTEN)
|
||||||
|
add_compile_options(-fsanitize=address,undefined -fno-omit-frame-pointer)
|
||||||
|
add_link_options(-fsanitize=address,undefined)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_subdirectory(third_party)
|
add_subdirectory(third_party)
|
||||||
|
|
||||||
add_library(regolith
|
add_library(regolith
|
||||||
src/context.c
|
src/context.c
|
||||||
|
src/nodes.c
|
||||||
|
src/props.c
|
||||||
|
src/types.c
|
||||||
src/layout_clay.c
|
src/layout_clay.c
|
||||||
src/text_stb.c
|
src/text_stb.c
|
||||||
src/lua_module.c
|
src/lua_module.c
|
||||||
|
|||||||
+94
-1
@@ -39,7 +39,10 @@ enum {
|
|||||||
RG_ERR_GENERIC = -1,
|
RG_ERR_GENERIC = -1,
|
||||||
RG_ERR_NO_MEMORY = -2,
|
RG_ERR_NO_MEMORY = -2,
|
||||||
RG_ERR_STATE = -3,
|
RG_ERR_STATE = -3,
|
||||||
RG_ERR_LUA = -4
|
RG_ERR_LUA = -4,
|
||||||
|
RG_ERR_HANDLE = -5,
|
||||||
|
RG_ERR_TYPE = -6,
|
||||||
|
RG_ERR_PROP = -7
|
||||||
};
|
};
|
||||||
|
|
||||||
enum { RG_LOG_TRACE = 0, RG_LOG_DEBUG, RG_LOG_INFO, RG_LOG_WARN, RG_LOG_ERROR };
|
enum { RG_LOG_TRACE = 0, RG_LOG_DEBUG, RG_LOG_INFO, RG_LOG_WARN, RG_LOG_ERROR };
|
||||||
@@ -72,6 +75,96 @@ RG_API rg_context *rg_create(const rg_config *config);
|
|||||||
RG_API void rg_destroy(rg_context *ctx);
|
RG_API void rg_destroy(rg_context *ctx);
|
||||||
RG_API const char *rg_last_error(const rg_context *ctx);
|
RG_API const char *rg_last_error(const rg_context *ctx);
|
||||||
|
|
||||||
|
typedef uint64_t rg_node;
|
||||||
|
#define RG_NODE_NONE ((rg_node)0)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
RG_NODE_ROOT = 0,
|
||||||
|
RG_NODE_BOX,
|
||||||
|
RG_NODE_HBOX,
|
||||||
|
RG_NODE_VBOX,
|
||||||
|
RG_NODE_PANEL,
|
||||||
|
RG_NODE_LABEL,
|
||||||
|
RG_NODE_BUTTON,
|
||||||
|
RG_NODE_TYPE_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
RG_VAL_NONE = 0,
|
||||||
|
RG_VAL_BOOL,
|
||||||
|
RG_VAL_INT,
|
||||||
|
RG_VAL_FLOAT,
|
||||||
|
RG_VAL_ENUM,
|
||||||
|
RG_VAL_COLOR,
|
||||||
|
RG_VAL_SIZE,
|
||||||
|
RG_VAL_VEC4,
|
||||||
|
RG_VAL_STRING
|
||||||
|
};
|
||||||
|
|
||||||
|
enum { RG_SIZE_FIT = 0, RG_SIZE_GROW, RG_SIZE_FIXED, RG_SIZE_PERCENT };
|
||||||
|
enum { RG_DIR_ROW = 0, RG_DIR_COLUMN };
|
||||||
|
enum { RG_ALIGN_START = 0, RG_ALIGN_CENTER, RG_ALIGN_END };
|
||||||
|
|
||||||
|
enum {
|
||||||
|
RG_PROP_DIRECTION = 0,
|
||||||
|
RG_PROP_WIDTH,
|
||||||
|
RG_PROP_HEIGHT,
|
||||||
|
RG_PROP_PADDING,
|
||||||
|
RG_PROP_GAP,
|
||||||
|
RG_PROP_ALIGN_X,
|
||||||
|
RG_PROP_ALIGN_Y,
|
||||||
|
RG_PROP_BACKGROUND,
|
||||||
|
RG_PROP_BORDER_COLOR,
|
||||||
|
RG_PROP_BORDER_WIDTH,
|
||||||
|
RG_PROP_CORNER_RADIUS,
|
||||||
|
RG_PROP_OPACITY,
|
||||||
|
RG_PROP_VISIBLE,
|
||||||
|
RG_PROP_TEXT,
|
||||||
|
RG_PROP_FONT,
|
||||||
|
RG_PROP_FONT_SIZE,
|
||||||
|
RG_PROP_COLOR,
|
||||||
|
RG_PROP_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct rg_value {
|
||||||
|
int32_t type;
|
||||||
|
int32_t i;
|
||||||
|
float f[4];
|
||||||
|
const char *s;
|
||||||
|
} rg_value;
|
||||||
|
|
||||||
|
RG_API int32_t rg_type_find(const char *name);
|
||||||
|
RG_API const char *rg_type_name(int32_t type);
|
||||||
|
|
||||||
|
RG_API rg_node rg_root(const rg_context *ctx);
|
||||||
|
RG_API rg_node rg_node_create(rg_context *ctx, int32_t type);
|
||||||
|
RG_API void rg_node_destroy(rg_context *ctx, rg_node node);
|
||||||
|
RG_API int rg_node_valid(const rg_context *ctx, rg_node node);
|
||||||
|
RG_API int32_t rg_node_type(const rg_context *ctx, rg_node node);
|
||||||
|
RG_API int rg_node_append(rg_context *ctx, rg_node parent, rg_node child);
|
||||||
|
RG_API int rg_node_insert_before(rg_context *ctx, rg_node parent, rg_node child,
|
||||||
|
rg_node ref);
|
||||||
|
RG_API int rg_node_detach(rg_context *ctx, rg_node node);
|
||||||
|
RG_API rg_node rg_node_parent(const rg_context *ctx, rg_node node);
|
||||||
|
RG_API rg_node rg_node_first_child(const rg_context *ctx, rg_node node);
|
||||||
|
RG_API rg_node rg_node_next_sibling(const rg_context *ctx, rg_node node);
|
||||||
|
|
||||||
|
RG_API int32_t rg_prop_find(const char *name);
|
||||||
|
RG_API const char *rg_prop_name(int32_t prop);
|
||||||
|
RG_API int32_t rg_prop_type(int32_t prop);
|
||||||
|
|
||||||
|
RG_API int rg_set(rg_context *ctx, rg_node node, int32_t prop,
|
||||||
|
const rg_value *value);
|
||||||
|
RG_API int rg_get(rg_context *ctx, rg_node node, int32_t prop, rg_value *out);
|
||||||
|
RG_API int rg_unset(rg_context *ctx, rg_node node, int32_t prop);
|
||||||
|
|
||||||
|
RG_API int rg_set_int(rg_context *ctx, rg_node node, int32_t prop, int32_t v);
|
||||||
|
RG_API int rg_set_float(rg_context *ctx, rg_node node, int32_t prop, float v);
|
||||||
|
RG_API int rg_set_string(rg_context *ctx, rg_node node, int32_t prop,
|
||||||
|
const char *v);
|
||||||
|
RG_API int rg_set_color(rg_context *ctx, rg_node node, int32_t prop, float r,
|
||||||
|
float g, float b, float a);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
usage: scripts/build.sh <command>
|
||||||
|
|
||||||
|
commands:
|
||||||
|
debug configure, build, and test into build/
|
||||||
|
release configure, build, and test into build-release/
|
||||||
|
asan configure, build, and test with ASan and UBSan into build-asan/
|
||||||
|
wasm configure, build, and test with Emscripten into build-wasm/
|
||||||
|
all debug, asan, then wasm
|
||||||
|
clean remove all build directories
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
local dir=$1 launcher=$2
|
||||||
|
shift 2
|
||||||
|
$launcher cmake -S . -B "$dir" -G Ninja "$@"
|
||||||
|
cmake --build "$dir"
|
||||||
|
ctest --test-dir "$dir" --output-on-failure
|
||||||
|
}
|
||||||
|
|
||||||
|
link_compile_commands() {
|
||||||
|
ln -sf build/compile_commands.json compile_commands.json
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
debug)
|
||||||
|
build build "" -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
link_compile_commands
|
||||||
|
;;
|
||||||
|
release)
|
||||||
|
build build-release "" -DCMAKE_BUILD_TYPE=Release
|
||||||
|
;;
|
||||||
|
asan)
|
||||||
|
build build-asan "" -DCMAKE_BUILD_TYPE=Debug -DREGOLITH_SANITIZE=ON
|
||||||
|
;;
|
||||||
|
wasm)
|
||||||
|
build build-wasm emcmake -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
"$0" debug
|
||||||
|
"$0" asan
|
||||||
|
"$0" wasm
|
||||||
|
;;
|
||||||
|
clean)
|
||||||
|
rm -rf build build-release build-asan build-wasm compile_commands.json
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
#include "regolith.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -102,6 +103,11 @@ rg_context *rg_create(const rg_config *config) {
|
|||||||
ctx->viewport_h = cfg.viewport_h;
|
ctx->viewport_h = cfg.viewport_h;
|
||||||
ctx->display_scale = cfg.display_scale;
|
ctx->display_scale = cfg.display_scale;
|
||||||
|
|
||||||
|
if(rg__nodes_init(ctx) != RG_OK) {
|
||||||
|
rg_destroy(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
rg__log(ctx, RG_LOG_DEBUG, "clay minimum memory: %zu bytes",
|
rg__log(ctx, RG_LOG_DEBUG, "clay minimum memory: %zu bytes",
|
||||||
rg__clay_min_memory());
|
rg__clay_min_memory());
|
||||||
rg__log(ctx, RG_LOG_INFO, "context created (%dx%d @ %.2fx)",
|
rg__log(ctx, RG_LOG_INFO, "context created (%dx%d @ %.2fx)",
|
||||||
@@ -116,7 +122,31 @@ void rg_destroy(rg_context *ctx) {
|
|||||||
if (!ctx)
|
if (!ctx)
|
||||||
return;
|
return;
|
||||||
rg__lua_close(ctx);
|
rg__lua_close(ctx);
|
||||||
|
rg__nodes_free(ctx);
|
||||||
rg__log(ctx, RG_LOG_INFO, "context destroyed");
|
rg__log(ctx, RG_LOG_INFO, "context destroyed");
|
||||||
a = ctx->alloc;
|
a = ctx->alloc;
|
||||||
a.free(ctx, sizeof *ctx, a.user);
|
a.free(ctx, sizeof *ctx, a.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void *rg__alloc(rg_context *ctx, size_t size) {
|
||||||
|
return ctx->alloc.alloc(size, ctx->alloc.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *rg__zalloc(rg_context *ctx, size_t size) {
|
||||||
|
void *p = rg__alloc(ctx, size);
|
||||||
|
if (p)
|
||||||
|
memset(p, 0, size);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *rg__realloc(rg_context *ctx, void *ptr, size_t old_size,
|
||||||
|
size_t new_size) {
|
||||||
|
if (!ptr)
|
||||||
|
return rg__alloc(ctx, new_size);
|
||||||
|
return ctx->alloc.realloc(ptr, old_size, new_size, ctx->alloc.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rg__free(rg_context *ctx, void *ptr, size_t size) {
|
||||||
|
if (ptr)
|
||||||
|
ctx->alloc.free(ptr, size, ctx->alloc.user);
|
||||||
|
}
|
||||||
+68
-2
@@ -1,7 +1,6 @@
|
|||||||
#ifndef RG_INTERNAL_H
|
#ifndef RG_INTERNAL_H
|
||||||
#define RG_INTERNAL_H
|
#define RG_INTERNAL_H
|
||||||
|
|
||||||
|
|
||||||
#include "regolith.h"
|
#include "regolith.h"
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
@@ -10,6 +9,49 @@
|
|||||||
#define RG_PRINTF(f, a)
|
#define RG_PRINTF(f, a)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define RG_NIL 0xFFFFFFFFu
|
||||||
|
|
||||||
|
enum { RG_PF_INHERIT = 1, RG_PF_LAYOUT = 2, RG_PF_ANIMATE = 4 };
|
||||||
|
enum { RG_TF_FOCUSABLE = 1, RG_TF_TEXT = 2 };
|
||||||
|
|
||||||
|
#define RG_V_BOOL(v) {RG_VAL_BOOL, (v), {0, 0, 0, 0}, NULL}
|
||||||
|
#define RG_V_ENUM(v) {RG_VAL_ENUM, (v), {0, 0, 0, 0}, NULL}
|
||||||
|
#define RG_V_FLOAT(v) {RG_VAL_FLOAT, 0, {(v), 0, 0, 0}, NULL}
|
||||||
|
#define RG_V_COLOR(r, g, b, a) {RG_VAL_COLOR, 0, {(r), (g), (b), (a)}, NULL}
|
||||||
|
#define RG_V_VEC4(a, b, c, d) {RG_VAL_VEC4, 0, {(a), (b), (c), (d)}, NULL}
|
||||||
|
#define RG_V_SIZE(mode, v) {RG_VAL_SIZE, (mode), {(v), 0, 0, 0}, NULL}
|
||||||
|
#define RG_V_STRING(s) {RG_VAL_STRING, 0, {0, 0, 0, 0}, (s)}
|
||||||
|
|
||||||
|
typedef struct rg_override {
|
||||||
|
int32_t prop;
|
||||||
|
rg_value value;
|
||||||
|
} rg_override;
|
||||||
|
|
||||||
|
typedef struct rg_prop_info {
|
||||||
|
const char *name;
|
||||||
|
int32_t type;
|
||||||
|
uint32_t flags;
|
||||||
|
rg_value def;
|
||||||
|
} rg_prop_info;
|
||||||
|
|
||||||
|
typedef struct rg_type_info {
|
||||||
|
const char *name;
|
||||||
|
uint32_t flags;
|
||||||
|
const rg_override *defaults;
|
||||||
|
int32_t default_count;
|
||||||
|
} rg_type_info;
|
||||||
|
|
||||||
|
typedef struct rg_node_data {
|
||||||
|
uint32_t index;
|
||||||
|
uint32_t generation;
|
||||||
|
uint32_t alive;
|
||||||
|
uint32_t parent, first_child, last_child, prev, next;
|
||||||
|
uint32_t next_free;
|
||||||
|
int32_t type;
|
||||||
|
rg_override *ov;
|
||||||
|
uint32_t ov_count, ov_cap;
|
||||||
|
} rg_node_data;
|
||||||
|
|
||||||
struct lua_State;
|
struct lua_State;
|
||||||
|
|
||||||
struct rg_context {
|
struct rg_context {
|
||||||
@@ -22,11 +64,35 @@ struct rg_context {
|
|||||||
float display_scale;
|
float display_scale;
|
||||||
char last_error[256];
|
char last_error[256];
|
||||||
struct lua_State *L;
|
struct lua_State *L;
|
||||||
|
|
||||||
|
rg_node_data **chunks;
|
||||||
|
uint32_t chunk_count;
|
||||||
|
uint32_t chunk_cap;
|
||||||
|
uint32_t slot_count;
|
||||||
|
uint32_t free_head;
|
||||||
|
uint32_t node_count;
|
||||||
|
rg_node root;
|
||||||
};
|
};
|
||||||
|
|
||||||
void rg__log(rg_context* ctx, int32_t level, const char* fmt, ...) RG_PRINTF(3, 4);
|
void rg__log(rg_context *ctx, int32_t level, const char *fmt, ...)
|
||||||
|
RG_PRINTF(3, 4);
|
||||||
int rg__fail(rg_context *ctx, int code, const char *fmt, ...) RG_PRINTF(3, 4);
|
int rg__fail(rg_context *ctx, int code, const char *fmt, ...) RG_PRINTF(3, 4);
|
||||||
|
|
||||||
|
void *rg__alloc(rg_context *ctx, size_t size);
|
||||||
|
void *rg__zalloc(rg_context *ctx, size_t size);
|
||||||
|
void *rg__realloc(rg_context *ctx, void *ptr, size_t old_size, size_t new_size);
|
||||||
|
void rg__free(rg_context *ctx, void *ptr, size_t size);
|
||||||
|
|
||||||
|
int rg__nodes_init(rg_context *ctx);
|
||||||
|
void rg__nodes_free(rg_context *ctx);
|
||||||
|
rg_node rg__node_create(rg_context *ctx, int32_t type);
|
||||||
|
rg_node_data *rg__node_resolve(const rg_context *ctx, rg_node node);
|
||||||
|
|
||||||
|
const rg_prop_info *rg__prop_info(int32_t prop);
|
||||||
|
const rg_type_info *rg__type_info(int32_t type);
|
||||||
|
const rg_value *rg__default_value(int32_t type, int32_t prop);
|
||||||
|
void rg__overrides_free(rg_context *ctx, rg_node_data *n);
|
||||||
|
|
||||||
size_t rg__clay_min_memory(void);
|
size_t rg__clay_min_memory(void);
|
||||||
void rg__lua_close(rg_context *ctx);
|
void rg__lua_close(rg_context *ctx);
|
||||||
|
|
||||||
|
|||||||
+276
@@ -0,0 +1,276 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#define CHUNK_SHIFT 8
|
||||||
|
#define CHUNK_SIZE (1u << CHUNK_SHIFT)
|
||||||
|
#define CHUNK_MASK (CHUNK_SIZE - 1u)
|
||||||
|
|
||||||
|
static rg_node_data *slot_at(const rg_context *ctx, uint32_t idx) {
|
||||||
|
return &ctx->chunks[idx >> CHUNK_SHIFT][idx & CHUNK_MASK];
|
||||||
|
}
|
||||||
|
|
||||||
|
static rg_node make_handle(const rg_node_data *n) {
|
||||||
|
return ((rg_node)n->generation << 32) | (rg_node)n->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rg_node handle_of(const rg_context *ctx, uint32_t idx) {
|
||||||
|
return idx == RG_NIL ? RG_NODE_NONE : make_handle(slot_at(ctx, idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node_data *rg__node_resolve(const rg_context *ctx, rg_node h) {
|
||||||
|
uint32_t idx = (uint32_t)(h & 0xFFFFFFFFu);
|
||||||
|
uint32_t gen = (uint32_t)(h >> 32);
|
||||||
|
rg_node_data *n;
|
||||||
|
|
||||||
|
if (h == RG_NODE_NONE || idx >= ctx->slot_count)
|
||||||
|
return NULL;
|
||||||
|
n = slot_at(ctx, idx);
|
||||||
|
if (!n->alive || n->generation != gen)
|
||||||
|
return NULL;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int grow_chunks(rg_context *ctx) {
|
||||||
|
rg_node_data *chunk;
|
||||||
|
|
||||||
|
if (ctx->chunk_count == ctx->chunk_cap) {
|
||||||
|
uint32_t cap = ctx->chunk_cap ? ctx->chunk_cap * 2 : 4;
|
||||||
|
rg_node_data **arr = rg__realloc(
|
||||||
|
ctx, ctx->chunks, ctx->chunk_cap * sizeof *arr, cap * sizeof *arr);
|
||||||
|
if (!arr)
|
||||||
|
return RG_ERR_NO_MEMORY;
|
||||||
|
ctx->chunks = arr;
|
||||||
|
ctx->chunk_cap = cap;
|
||||||
|
}
|
||||||
|
chunk = rg__zalloc(ctx, CHUNK_SIZE * sizeof *chunk);
|
||||||
|
if (!chunk)
|
||||||
|
return RG_ERR_NO_MEMORY;
|
||||||
|
ctx->chunks[ctx->chunk_count++] = chunk;
|
||||||
|
return RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rg_node_data *slot_alloc(rg_context *ctx) {
|
||||||
|
rg_node_data *n;
|
||||||
|
uint32_t idx;
|
||||||
|
|
||||||
|
if (ctx->free_head != RG_NIL) {
|
||||||
|
idx = ctx->free_head;
|
||||||
|
n = slot_at(ctx, idx);
|
||||||
|
ctx->free_head = n->next_free;
|
||||||
|
} else {
|
||||||
|
idx = ctx->slot_count;
|
||||||
|
if ((idx >> CHUNK_SHIFT) == ctx->chunk_count && grow_chunks(ctx) != RG_OK)
|
||||||
|
return NULL;
|
||||||
|
n = slot_at(ctx, idx);
|
||||||
|
ctx->slot_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
n->index = idx;
|
||||||
|
if (n->generation == 0)
|
||||||
|
n->generation = 1;
|
||||||
|
n->alive = 1;
|
||||||
|
n->parent = n->first_child = n->last_child = RG_NIL;
|
||||||
|
n->prev = n->next = n->next_free = RG_NIL;
|
||||||
|
n->ov = NULL;
|
||||||
|
n->ov_count = n->ov_cap = 0;
|
||||||
|
ctx->node_count++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void release_slot(rg_context *ctx, rg_node_data *n) {
|
||||||
|
rg__log(ctx, RG_LOG_TRACE, "node destroyed: %s (slot %u)",
|
||||||
|
rg_type_name(n->type), (unsigned)n->index);
|
||||||
|
rg__overrides_free(ctx, n);
|
||||||
|
n->alive = 0;
|
||||||
|
if (++n->generation == 0)
|
||||||
|
n->generation = 1;
|
||||||
|
n->next_free = ctx->free_head;
|
||||||
|
ctx->free_head = n->index;
|
||||||
|
ctx->node_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unlink_node(rg_context *ctx, rg_node_data *n) {
|
||||||
|
rg_node_data *p;
|
||||||
|
|
||||||
|
if (n->parent == RG_NIL)
|
||||||
|
return;
|
||||||
|
p = slot_at(ctx, n->parent);
|
||||||
|
if (n->prev != RG_NIL)
|
||||||
|
slot_at(ctx, n->prev)->next = n->next;
|
||||||
|
else
|
||||||
|
p->first_child = n->next;
|
||||||
|
if (n->next != RG_NIL)
|
||||||
|
slot_at(ctx, n->next)->prev = n->prev;
|
||||||
|
else
|
||||||
|
p->last_child = n->prev;
|
||||||
|
n->parent = n->prev = n->next = RG_NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void destroy_subtree(rg_context *ctx, rg_node_data *top) {
|
||||||
|
uint32_t top_idx = top->index;
|
||||||
|
uint32_t cur = top_idx;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
rg_node_data *n = slot_at(ctx, cur);
|
||||||
|
uint32_t parent;
|
||||||
|
|
||||||
|
if (n->first_child != RG_NIL) {
|
||||||
|
cur = n->first_child;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parent = n->parent;
|
||||||
|
unlink_node(ctx, n);
|
||||||
|
release_slot(ctx, n);
|
||||||
|
if (cur == top_idx)
|
||||||
|
break;
|
||||||
|
cur = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_self_or_ancestor(const rg_context *ctx, const rg_node_data *anc,
|
||||||
|
const rg_node_data *n) {
|
||||||
|
while (n) {
|
||||||
|
if (n == anc)
|
||||||
|
return 1;
|
||||||
|
n = n->parent == RG_NIL ? NULL : slot_at(ctx, n->parent);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node rg__node_create(rg_context *ctx, int32_t type) {
|
||||||
|
rg_node_data *n = slot_alloc(ctx);
|
||||||
|
|
||||||
|
if (!n) {
|
||||||
|
rg__fail(ctx, RG_ERR_NO_MEMORY, "node allocation failed");
|
||||||
|
return RG_NODE_NONE;
|
||||||
|
}
|
||||||
|
n->type = type;
|
||||||
|
rg__log(ctx, RG_LOG_TRACE, "node created: %s (slot %u)", rg_type_name(type),
|
||||||
|
(unsigned)n->index);
|
||||||
|
return make_handle(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg__nodes_init(rg_context *ctx) {
|
||||||
|
ctx->free_head = RG_NIL;
|
||||||
|
ctx->root = rg__node_create(ctx, RG_NODE_ROOT);
|
||||||
|
return ctx->root == RG_NODE_NONE ? RG_ERR_NO_MEMORY : RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rg__nodes_free(rg_context *ctx) {
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < ctx->slot_count; i++) {
|
||||||
|
rg_node_data *n = slot_at(ctx, i);
|
||||||
|
if (n->alive)
|
||||||
|
rg__overrides_free(ctx, n);
|
||||||
|
}
|
||||||
|
for (i = 0; i < ctx->chunk_count; i++)
|
||||||
|
rg__free(ctx, ctx->chunks[i], CHUNK_SIZE * sizeof(rg_node_data));
|
||||||
|
rg__free(ctx, ctx->chunks, ctx->chunk_cap * sizeof *ctx->chunks);
|
||||||
|
ctx->chunks = NULL;
|
||||||
|
ctx->chunk_count = ctx->chunk_cap = ctx->slot_count = ctx->node_count = 0;
|
||||||
|
ctx->root = RG_NODE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node rg_root(const rg_context *ctx) { return ctx->root; }
|
||||||
|
|
||||||
|
rg_node rg_node_create(rg_context *ctx, int32_t type) {
|
||||||
|
if (type <= RG_NODE_ROOT || type >= RG_NODE_TYPE_COUNT) {
|
||||||
|
rg__fail(ctx, RG_ERR_TYPE, "cannot create node of type %d", (int)type);
|
||||||
|
return RG_NODE_NONE;
|
||||||
|
}
|
||||||
|
return rg__node_create(ctx, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rg_node_destroy(rg_context *ctx, rg_node node) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
|
||||||
|
if (!n) {
|
||||||
|
rg__log(ctx, RG_LOG_WARN, "destroy of a stale or invalid handle ignored");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (node == ctx->root) {
|
||||||
|
rg__log(ctx, RG_LOG_WARN, "the root node cannot be destroyed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unlink_node(ctx, n);
|
||||||
|
destroy_subtree(ctx, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_node_valid(const rg_context *ctx, rg_node node) {
|
||||||
|
return rg__node_resolve(ctx, node) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t rg_node_type(const rg_context *ctx, rg_node node) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
return n ? n->type : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_node_insert_before(rg_context *ctx, rg_node parent, rg_node child,
|
||||||
|
rg_node ref) {
|
||||||
|
rg_node_data *p = rg__node_resolve(ctx, parent);
|
||||||
|
rg_node_data *c = rg__node_resolve(ctx, child);
|
||||||
|
rg_node_data *r = NULL;
|
||||||
|
|
||||||
|
if (!p || !c)
|
||||||
|
return rg__fail(ctx, RG_ERR_HANDLE, "stale or invalid handle");
|
||||||
|
if (child == ctx->root)
|
||||||
|
return rg__fail(ctx, RG_ERR_STATE, "the root cannot be a child");
|
||||||
|
if (ref != RG_NODE_NONE) {
|
||||||
|
r = rg__node_resolve(ctx, ref);
|
||||||
|
if (!r)
|
||||||
|
return rg__fail(ctx, RG_ERR_HANDLE, "stale or invalid reference handle");
|
||||||
|
if (r == c || r->parent != p->index)
|
||||||
|
return rg__fail(ctx, RG_ERR_STATE,
|
||||||
|
"reference is not a child of the parent");
|
||||||
|
}
|
||||||
|
if (is_self_or_ancestor(ctx, c, p))
|
||||||
|
return rg__fail(ctx, RG_ERR_STATE, "insert would create a cycle");
|
||||||
|
|
||||||
|
unlink_node(ctx, c);
|
||||||
|
c->parent = p->index;
|
||||||
|
if (r) {
|
||||||
|
c->next = r->index;
|
||||||
|
c->prev = r->prev;
|
||||||
|
if (r->prev != RG_NIL)
|
||||||
|
slot_at(ctx, r->prev)->next = c->index;
|
||||||
|
else
|
||||||
|
p->first_child = c->index;
|
||||||
|
r->prev = c->index;
|
||||||
|
} else {
|
||||||
|
c->prev = p->last_child;
|
||||||
|
if (p->last_child != RG_NIL)
|
||||||
|
slot_at(ctx, p->last_child)->next = c->index;
|
||||||
|
else
|
||||||
|
p->first_child = c->index;
|
||||||
|
p->last_child = c->index;
|
||||||
|
}
|
||||||
|
return RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_node_append(rg_context *ctx, rg_node parent, rg_node child) {
|
||||||
|
return rg_node_insert_before(ctx, parent, child, RG_NODE_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_node_detach(rg_context *ctx, rg_node node) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
|
||||||
|
if (!n)
|
||||||
|
return rg__fail(ctx, RG_ERR_HANDLE, "stale or invalid handle");
|
||||||
|
unlink_node(ctx, n);
|
||||||
|
return RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node rg_node_parent(const rg_context *ctx, rg_node node) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
return n ? handle_of(ctx, n->parent) : RG_NODE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node rg_node_first_child(const rg_context *ctx, rg_node node) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
return n ? handle_of(ctx, n->first_child) : RG_NODE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node rg_node_next_sibling(const rg_context *ctx, rg_node node) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
return n ? handle_of(ctx, n->next) : RG_NODE_NONE;
|
||||||
|
}
|
||||||
+251
@@ -0,0 +1,251 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static const rg_prop_info prop_table[RG_PROP_COUNT] = {
|
||||||
|
[RG_PROP_DIRECTION] = {"direction", RG_VAL_ENUM, RG_PF_LAYOUT,
|
||||||
|
RG_V_ENUM(RG_DIR_ROW)},
|
||||||
|
[RG_PROP_WIDTH] = {"width", RG_VAL_SIZE, RG_PF_LAYOUT | RG_PF_ANIMATE,
|
||||||
|
RG_V_SIZE(RG_SIZE_FIT, 0)},
|
||||||
|
[RG_PROP_HEIGHT] = {"height", RG_VAL_SIZE, RG_PF_LAYOUT | RG_PF_ANIMATE,
|
||||||
|
RG_V_SIZE(RG_SIZE_FIT, 0)},
|
||||||
|
[RG_PROP_PADDING] = {"padding", RG_VAL_VEC4, RG_PF_LAYOUT | RG_PF_ANIMATE,
|
||||||
|
RG_V_VEC4(0, 0, 0, 0)},
|
||||||
|
[RG_PROP_GAP] = {"gap", RG_VAL_FLOAT, RG_PF_LAYOUT | RG_PF_ANIMATE,
|
||||||
|
RG_V_FLOAT(0)},
|
||||||
|
[RG_PROP_ALIGN_X] = {"align_x", RG_VAL_ENUM, RG_PF_LAYOUT,
|
||||||
|
RG_V_ENUM(RG_ALIGN_START)},
|
||||||
|
[RG_PROP_ALIGN_Y] = {"align_y", RG_VAL_ENUM, RG_PF_LAYOUT,
|
||||||
|
RG_V_ENUM(RG_ALIGN_START)},
|
||||||
|
[RG_PROP_BACKGROUND] = {"background", RG_VAL_COLOR, RG_PF_ANIMATE,
|
||||||
|
RG_V_COLOR(0, 0, 0, 0)},
|
||||||
|
[RG_PROP_BORDER_COLOR] = {"border_color", RG_VAL_COLOR, RG_PF_ANIMATE,
|
||||||
|
RG_V_COLOR(0, 0, 0, 0)},
|
||||||
|
[RG_PROP_BORDER_WIDTH] = {"border_width", RG_VAL_VEC4,
|
||||||
|
RG_PF_LAYOUT | RG_PF_ANIMATE,
|
||||||
|
RG_V_VEC4(0, 0, 0, 0)},
|
||||||
|
[RG_PROP_CORNER_RADIUS] = {"corner_radius", RG_VAL_VEC4, RG_PF_ANIMATE,
|
||||||
|
RG_V_VEC4(0, 0, 0, 0)},
|
||||||
|
[RG_PROP_OPACITY] = {"opacity", RG_VAL_FLOAT, RG_PF_ANIMATE, RG_V_FLOAT(1)},
|
||||||
|
[RG_PROP_VISIBLE] = {"visible", RG_VAL_BOOL, RG_PF_LAYOUT, RG_V_BOOL(1)},
|
||||||
|
[RG_PROP_TEXT] = {"text", RG_VAL_STRING, RG_PF_LAYOUT, RG_V_STRING("")},
|
||||||
|
[RG_PROP_FONT] = {"font", RG_VAL_STRING, RG_PF_LAYOUT | RG_PF_INHERIT,
|
||||||
|
RG_V_STRING("")},
|
||||||
|
[RG_PROP_FONT_SIZE] = {"font_size", RG_VAL_FLOAT,
|
||||||
|
RG_PF_LAYOUT | RG_PF_INHERIT, RG_V_FLOAT(16)},
|
||||||
|
[RG_PROP_COLOR] = {"color", RG_VAL_COLOR, RG_PF_INHERIT | RG_PF_ANIMATE,
|
||||||
|
RG_V_COLOR(1, 1, 1, 1)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *val_type_name(int32_t t) {
|
||||||
|
static const char *names[] = {"none", "bool", "int", "float", "enum",
|
||||||
|
"color", "size", "vec4", "string"};
|
||||||
|
return t >= 0 && t <= RG_VAL_STRING ? names[t] : "invalid";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void value_init(rg_value *v, int32_t type) {
|
||||||
|
memset(v, 0, sizeof *v);
|
||||||
|
v->type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rg_prop_info *rg__prop_info(int32_t prop) {
|
||||||
|
if (prop < 0 || prop >= RG_PROP_COUNT)
|
||||||
|
return NULL;
|
||||||
|
return &prop_table[prop];
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t rg_prop_find(const char *name) {
|
||||||
|
int32_t i;
|
||||||
|
|
||||||
|
if (!name)
|
||||||
|
return -1;
|
||||||
|
for (i = 0; i < RG_PROP_COUNT; i++)
|
||||||
|
if (strcmp(prop_table[i].name, name) == 0)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *rg_prop_name(int32_t prop) {
|
||||||
|
const rg_prop_info *info = rg__prop_info(prop);
|
||||||
|
return info ? info->name : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t rg_prop_type(int32_t prop) {
|
||||||
|
const rg_prop_info *info = rg__prop_info(prop);
|
||||||
|
return info ? info->type : RG_VAL_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rg_value *rg__default_value(int32_t type, int32_t prop) {
|
||||||
|
const rg_type_info *t = rg__type_info(type);
|
||||||
|
int32_t i;
|
||||||
|
|
||||||
|
if (t)
|
||||||
|
for (i = 0; i < t->default_count; i++)
|
||||||
|
if (t->defaults[i].prop == prop)
|
||||||
|
return &t->defaults[i].value;
|
||||||
|
return &prop_table[prop].def;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *dup_string(rg_context *ctx, const char *s) {
|
||||||
|
size_t len = strlen(s) + 1;
|
||||||
|
char *p = rg__alloc(ctx, len);
|
||||||
|
|
||||||
|
if (p)
|
||||||
|
memcpy(p, s, len);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drop_value(rg_context *ctx, rg_value *v) {
|
||||||
|
if (v->type == RG_VAL_STRING && v->s) {
|
||||||
|
rg__free(ctx, (void *)v->s, strlen(v->s) + 1);
|
||||||
|
v->s = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rg__overrides_free(rg_context *ctx, rg_node_data *n) {
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < n->ov_count; i++)
|
||||||
|
drop_value(ctx, &n->ov[i].value);
|
||||||
|
rg__free(ctx, n->ov, n->ov_cap * sizeof *n->ov);
|
||||||
|
n->ov = NULL;
|
||||||
|
n->ov_count = n->ov_cap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rg_override *find_override(const rg_node_data *n, int32_t prop) {
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < n->ov_count; i++)
|
||||||
|
if (n->ov[i].prop == prop)
|
||||||
|
return &n->ov[i];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_set(rg_context *ctx, rg_node node, int32_t prop, const rg_value *value) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
const rg_prop_info *info = rg__prop_info(prop);
|
||||||
|
rg_override *o;
|
||||||
|
rg_value v;
|
||||||
|
|
||||||
|
if (!n)
|
||||||
|
return rg__fail(ctx, RG_ERR_HANDLE, "set: stale or invalid handle");
|
||||||
|
if (!info)
|
||||||
|
return rg__fail(ctx, RG_ERR_PROP, "set: unknown property %d", (int)prop);
|
||||||
|
if (!value || value->type != info->type)
|
||||||
|
return rg__fail(ctx, RG_ERR_TYPE, "set: property '%s' expects %s",
|
||||||
|
info->name, val_type_name(info->type));
|
||||||
|
if (value->type == RG_VAL_STRING && !value->s)
|
||||||
|
return rg__fail(ctx, RG_ERR_TYPE, "set: property '%s' got a null string",
|
||||||
|
info->name);
|
||||||
|
if (value->type == RG_VAL_SIZE &&
|
||||||
|
(value->i < RG_SIZE_FIT || value->i > RG_SIZE_PERCENT))
|
||||||
|
return rg__fail(ctx, RG_ERR_TYPE,
|
||||||
|
"set: property '%s' got an invalid size mode", info->name);
|
||||||
|
|
||||||
|
v = *value;
|
||||||
|
if (v.type == RG_VAL_STRING) {
|
||||||
|
v.s = dup_string(ctx, value->s);
|
||||||
|
if (!v.s)
|
||||||
|
return rg__fail(ctx, RG_ERR_NO_MEMORY, "set: string allocation failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
o = find_override(n, prop);
|
||||||
|
if (o) {
|
||||||
|
drop_value(ctx, &o->value);
|
||||||
|
o->value = v;
|
||||||
|
} else {
|
||||||
|
if (n->ov_count == n->ov_cap) {
|
||||||
|
uint32_t cap = n->ov_cap ? n->ov_cap * 2 : 4;
|
||||||
|
rg_override *arr =
|
||||||
|
rg__realloc(ctx, n->ov, n->ov_cap * sizeof *arr, cap * sizeof *arr);
|
||||||
|
if (!arr) {
|
||||||
|
drop_value(ctx, &v);
|
||||||
|
return rg__fail(ctx, RG_ERR_NO_MEMORY,
|
||||||
|
"set: override allocation failed");
|
||||||
|
}
|
||||||
|
n->ov = arr;
|
||||||
|
n->ov_cap = cap;
|
||||||
|
}
|
||||||
|
n->ov[n->ov_count].prop = prop;
|
||||||
|
n->ov[n->ov_count].value = v;
|
||||||
|
n->ov_count++;
|
||||||
|
}
|
||||||
|
rg__log(ctx, RG_LOG_TRACE, "set %s on %s", info->name, rg_type_name(n->type));
|
||||||
|
return RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_get(rg_context *ctx, rg_node node, int32_t prop, rg_value *out) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
const rg_override *o;
|
||||||
|
|
||||||
|
if (!n)
|
||||||
|
return rg__fail(ctx, RG_ERR_HANDLE, "get: stale or invalid handle");
|
||||||
|
if (!rg__prop_info(prop))
|
||||||
|
return rg__fail(ctx, RG_ERR_PROP, "get: unknown property %d", (int)prop);
|
||||||
|
if (!out)
|
||||||
|
return rg__fail(ctx, RG_ERR_STATE, "get: null output");
|
||||||
|
|
||||||
|
o = find_override(n, prop);
|
||||||
|
*out = o ? o->value : *rg__default_value(n->type, prop);
|
||||||
|
return RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_unset(rg_context *ctx, rg_node node, int32_t prop) {
|
||||||
|
rg_node_data *n = rg__node_resolve(ctx, node);
|
||||||
|
rg_override *o;
|
||||||
|
|
||||||
|
if (!n)
|
||||||
|
return rg__fail(ctx, RG_ERR_HANDLE, "unset: stale or invalid handle");
|
||||||
|
if (!rg__prop_info(prop))
|
||||||
|
return rg__fail(ctx, RG_ERR_PROP, "unset: unknown property %d", (int)prop);
|
||||||
|
|
||||||
|
o = find_override(n, prop);
|
||||||
|
if (o) {
|
||||||
|
drop_value(ctx, &o->value);
|
||||||
|
*o = n->ov[--n->ov_count];
|
||||||
|
}
|
||||||
|
return RG_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_set_int(rg_context *ctx, rg_node node, int32_t prop, int32_t v) {
|
||||||
|
const rg_prop_info *info = rg__prop_info(prop);
|
||||||
|
rg_value val;
|
||||||
|
|
||||||
|
if (!info)
|
||||||
|
return rg__fail(ctx, RG_ERR_PROP, "set: unknown property %d", (int)prop);
|
||||||
|
if (info->type != RG_VAL_BOOL && info->type != RG_VAL_INT &&
|
||||||
|
info->type != RG_VAL_ENUM)
|
||||||
|
return rg__fail(ctx, RG_ERR_TYPE,
|
||||||
|
"set: property '%s' is not an integer type", info->name);
|
||||||
|
value_init(&val, info->type);
|
||||||
|
val.i = v;
|
||||||
|
return rg_set(ctx, node, prop, &val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_set_float(rg_context *ctx, rg_node node, int32_t prop, float v) {
|
||||||
|
rg_value val;
|
||||||
|
|
||||||
|
value_init(&val, RG_VAL_FLOAT);
|
||||||
|
val.f[0] = v;
|
||||||
|
return rg_set(ctx, node, prop, &val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_set_string(rg_context *ctx, rg_node node, int32_t prop, const char *v) {
|
||||||
|
rg_value val;
|
||||||
|
|
||||||
|
value_init(&val, RG_VAL_STRING);
|
||||||
|
val.s = v;
|
||||||
|
return rg_set(ctx, node, prop, &val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int rg_set_color(rg_context *ctx, rg_node node, int32_t prop, float r, float g,
|
||||||
|
float b, float a) {
|
||||||
|
rg_value val;
|
||||||
|
|
||||||
|
value_init(&val, RG_VAL_COLOR);
|
||||||
|
val.f[0] = r;
|
||||||
|
val.f[1] = g;
|
||||||
|
val.f[2] = b;
|
||||||
|
val.f[3] = a;
|
||||||
|
return rg_set(ctx, node, prop, &val);
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define COUNT(a) ((int32_t)(sizeof(a) / sizeof((a)[0])))
|
||||||
|
|
||||||
|
static const rg_override root_defaults[] = {
|
||||||
|
{RG_PROP_WIDTH, RG_V_SIZE(RG_SIZE_GROW, 0)},
|
||||||
|
{RG_PROP_HEIGHT, RG_V_SIZE(RG_SIZE_GROW, 0)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rg_override hbox_defaults[] = {
|
||||||
|
{RG_PROP_DIRECTION, RG_V_ENUM(RG_DIR_ROW)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rg_override vbox_defaults[] = {
|
||||||
|
{RG_PROP_DIRECTION, RG_V_ENUM(RG_DIR_COLUMN)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rg_override panel_defaults[] = {
|
||||||
|
{RG_PROP_PADDING, RG_V_VEC4(8, 8, 8, 8)},
|
||||||
|
{RG_PROP_BACKGROUND, RG_V_COLOR(0.15f, 0.15f, 0.18f, 1.0f)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rg_override button_defaults[] = {
|
||||||
|
{RG_PROP_PADDING, RG_V_VEC4(8, 16, 8, 16)},
|
||||||
|
{RG_PROP_BACKGROUND, RG_V_COLOR(0.25f, 0.25f, 0.30f, 1.0f)},
|
||||||
|
{RG_PROP_CORNER_RADIUS, RG_V_VEC4(4, 4, 4, 4)},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const rg_type_info type_table[RG_NODE_TYPE_COUNT] = {
|
||||||
|
[RG_NODE_ROOT] = {"Root", 0, root_defaults, COUNT(root_defaults)},
|
||||||
|
[RG_NODE_BOX] = {"Box", 0, NULL, 0},
|
||||||
|
[RG_NODE_HBOX] = {"HBox", 0, hbox_defaults, COUNT(hbox_defaults)},
|
||||||
|
[RG_NODE_VBOX] = {"VBox", 0, vbox_defaults, COUNT(vbox_defaults)},
|
||||||
|
[RG_NODE_PANEL] = {"Panel", 0, panel_defaults, COUNT(panel_defaults)},
|
||||||
|
[RG_NODE_LABEL] = {"Label", RG_TF_TEXT, NULL, 0},
|
||||||
|
[RG_NODE_BUTTON] = {"Button", RG_TF_TEXT | RG_TF_FOCUSABLE, button_defaults,
|
||||||
|
COUNT(button_defaults)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const rg_type_info *rg__type_info(int32_t type) {
|
||||||
|
if (type < 0 || type >= RG_NODE_TYPE_COUNT)
|
||||||
|
return NULL;
|
||||||
|
return &type_table[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t rg_type_find(const char *name) {
|
||||||
|
int32_t i;
|
||||||
|
|
||||||
|
if (!name)
|
||||||
|
return -1;
|
||||||
|
for (i = 0; i < RG_NODE_TYPE_COUNT; i++)
|
||||||
|
if (strcmp(type_table[i].name, name) == 0)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *rg_type_name(int32_t type) {
|
||||||
|
const rg_type_info *t = rg__type_info(type);
|
||||||
|
return t ? t->name : "invalid";
|
||||||
|
}
|
||||||
@@ -7,3 +7,7 @@ if(TARGET SDL3::SDL3)
|
|||||||
target_link_libraries(smoke_sdl3 PRIVATE SDL3::SDL3)
|
target_link_libraries(smoke_sdl3 PRIVATE SDL3::SDL3)
|
||||||
add_test(NAME smoke_sdl3 COMMAND smoke_sdl3)
|
add_test(NAME smoke_sdl3 COMMAND smoke_sdl3)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_executable(test_nodes nodes.c)
|
||||||
|
target_link_libraries(test_nodes PRIVATE regolith)
|
||||||
|
add_test(NAME nodes COMMAND test_nodes)
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
#include "testing.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static int child_count(rg_context *ctx, rg_node parent) {
|
||||||
|
int n = 0;
|
||||||
|
rg_node c;
|
||||||
|
|
||||||
|
for (c = rg_node_first_child(ctx, parent); c != RG_NODE_NONE;
|
||||||
|
c = rg_node_next_sibling(ctx, c))
|
||||||
|
n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_tree(rg_context *ctx) {
|
||||||
|
rg_node root = rg_root(ctx);
|
||||||
|
rg_node a = rg_node_create(ctx, RG_NODE_VBOX);
|
||||||
|
rg_node b = rg_node_create(ctx, RG_NODE_LABEL);
|
||||||
|
rg_node c = rg_node_create(ctx, RG_NODE_BUTTON);
|
||||||
|
rg_node d = rg_node_create(ctx, RG_NODE_LABEL);
|
||||||
|
|
||||||
|
CHECK(rg_node_valid(ctx, root));
|
||||||
|
CHECK(rg_node_type(ctx, root) == RG_NODE_ROOT);
|
||||||
|
CHECK(rg_node_create(ctx, RG_NODE_ROOT) == RG_NODE_NONE);
|
||||||
|
|
||||||
|
CHECK(rg_node_append(ctx, root, a) == RG_OK);
|
||||||
|
CHECK(rg_node_append(ctx, a, b) == RG_OK);
|
||||||
|
CHECK(rg_node_append(ctx, a, c) == RG_OK);
|
||||||
|
CHECK(rg_node_insert_before(ctx, a, d, c) == RG_OK);
|
||||||
|
|
||||||
|
CHECK(child_count(ctx, a) == 3);
|
||||||
|
CHECK(rg_node_first_child(ctx, a) == b);
|
||||||
|
CHECK(rg_node_next_sibling(ctx, b) == d);
|
||||||
|
CHECK(rg_node_next_sibling(ctx, d) == c);
|
||||||
|
CHECK(rg_node_next_sibling(ctx, c) == RG_NODE_NONE);
|
||||||
|
CHECK(rg_node_parent(ctx, d) == a);
|
||||||
|
|
||||||
|
CHECK(rg_node_append(ctx, a, b) == RG_OK);
|
||||||
|
CHECK(rg_node_next_sibling(ctx, c) == b);
|
||||||
|
CHECK(child_count(ctx, a) == 3);
|
||||||
|
|
||||||
|
CHECK(rg_node_append(ctx, b, a) == RG_ERR_STATE);
|
||||||
|
CHECK(rg_node_append(ctx, a, a) == RG_ERR_STATE);
|
||||||
|
CHECK(rg_node_append(ctx, a, root) == RG_ERR_STATE);
|
||||||
|
CHECK(rg_node_insert_before(ctx, root, b, c) == RG_ERR_STATE);
|
||||||
|
|
||||||
|
CHECK(rg_node_detach(ctx, d) == RG_OK);
|
||||||
|
CHECK(rg_node_parent(ctx, d) == RG_NODE_NONE);
|
||||||
|
CHECK(child_count(ctx, a) == 2);
|
||||||
|
|
||||||
|
rg_node_destroy(ctx, a);
|
||||||
|
rg_node_destroy(ctx, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_handles(rg_context *ctx) {
|
||||||
|
rg_node root = rg_root(ctx);
|
||||||
|
rg_node a = rg_node_create(ctx, RG_NODE_PANEL);
|
||||||
|
rg_node b = rg_node_create(ctx, RG_NODE_LABEL);
|
||||||
|
rg_node c = rg_node_create(ctx, RG_NODE_LABEL);
|
||||||
|
rg_node fresh;
|
||||||
|
|
||||||
|
CHECK(rg_node_append(ctx, root, a) == RG_OK);
|
||||||
|
CHECK(rg_node_append(ctx, a, b) == RG_OK);
|
||||||
|
CHECK(rg_node_append(ctx, b, c) == RG_OK);
|
||||||
|
|
||||||
|
rg_node_destroy(ctx, a);
|
||||||
|
CHECK(!rg_node_valid(ctx, a));
|
||||||
|
CHECK(!rg_node_valid(ctx, b));
|
||||||
|
CHECK(!rg_node_valid(ctx, c));
|
||||||
|
CHECK(rg_set_float(ctx, b, RG_PROP_GAP, 1.0f) == RG_ERR_HANDLE);
|
||||||
|
CHECK(child_count(ctx, root) == 0);
|
||||||
|
|
||||||
|
fresh = rg_node_create(ctx, RG_NODE_BOX);
|
||||||
|
CHECK(fresh != a && fresh != b && fresh != c);
|
||||||
|
CHECK(!rg_node_valid(ctx, a));
|
||||||
|
CHECK(rg_node_valid(ctx, fresh));
|
||||||
|
|
||||||
|
rg_node_destroy(ctx, root);
|
||||||
|
CHECK(rg_node_valid(ctx, root));
|
||||||
|
rg_node_destroy(ctx, fresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_props(rg_context *ctx) {
|
||||||
|
rg_node hbox = rg_node_create(ctx, RG_NODE_HBOX);
|
||||||
|
rg_node vbox = rg_node_create(ctx, RG_NODE_VBOX);
|
||||||
|
rg_node btn = rg_node_create(ctx, RG_NODE_BUTTON);
|
||||||
|
rg_node lbl = rg_node_create(ctx, RG_NODE_LABEL);
|
||||||
|
rg_value v;
|
||||||
|
char buf[16];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < RG_PROP_COUNT; i++) {
|
||||||
|
CHECK(rg_prop_name(i) != NULL);
|
||||||
|
CHECK(rg_prop_find(rg_prop_name(i)) == i);
|
||||||
|
CHECK(rg_prop_type(i) != RG_VAL_NONE);
|
||||||
|
}
|
||||||
|
CHECK(rg_prop_find("no_such_prop") == -1);
|
||||||
|
CHECK(rg_type_find("Button") == RG_NODE_BUTTON);
|
||||||
|
CHECK(rg_type_find("Nope") == -1);
|
||||||
|
|
||||||
|
CHECK(rg_get(ctx, hbox, RG_PROP_DIRECTION, &v) == RG_OK && v.i == RG_DIR_ROW);
|
||||||
|
CHECK(rg_get(ctx, vbox, RG_PROP_DIRECTION, &v) == RG_OK &&
|
||||||
|
v.i == RG_DIR_COLUMN);
|
||||||
|
CHECK(rg_get(ctx, btn, RG_PROP_PADDING, &v) == RG_OK);
|
||||||
|
CHECK(v.f[0] == 8.0f && v.f[1] == 16.0f && v.f[2] == 8.0f && v.f[3] == 16.0f);
|
||||||
|
CHECK(rg_get(ctx, lbl, RG_PROP_OPACITY, &v) == RG_OK && v.f[0] == 1.0f);
|
||||||
|
CHECK(rg_get(ctx, lbl, RG_PROP_TEXT, &v) == RG_OK && strcmp(v.s, "") == 0);
|
||||||
|
|
||||||
|
CHECK(rg_set_int(ctx, vbox, RG_PROP_DIRECTION, RG_DIR_ROW) == RG_OK);
|
||||||
|
CHECK(rg_get(ctx, vbox, RG_PROP_DIRECTION, &v) == RG_OK && v.i == RG_DIR_ROW);
|
||||||
|
CHECK(rg_unset(ctx, vbox, RG_PROP_DIRECTION) == RG_OK);
|
||||||
|
CHECK(rg_get(ctx, vbox, RG_PROP_DIRECTION, &v) == RG_OK &&
|
||||||
|
v.i == RG_DIR_COLUMN);
|
||||||
|
CHECK(rg_unset(ctx, vbox, RG_PROP_DIRECTION) == RG_OK);
|
||||||
|
|
||||||
|
CHECK(rg_set_color(ctx, btn, RG_PROP_BACKGROUND, 1, 0, 0, 1) == RG_OK);
|
||||||
|
CHECK(rg_get(ctx, btn, RG_PROP_BACKGROUND, &v) == RG_OK && v.f[0] == 1.0f &&
|
||||||
|
v.f[1] == 0.0f);
|
||||||
|
|
||||||
|
CHECK(rg_set_float(ctx, btn, RG_PROP_BACKGROUND, 1.0f) == RG_ERR_TYPE);
|
||||||
|
CHECK(rg_set_int(ctx, btn, RG_PROP_GAP, 1) == RG_ERR_TYPE);
|
||||||
|
CHECK(rg_set(ctx, btn, RG_PROP_GAP, NULL) == RG_ERR_TYPE);
|
||||||
|
CHECK(rg_set_string(ctx, btn, RG_PROP_TEXT, NULL) == RG_ERR_TYPE);
|
||||||
|
CHECK(rg_set_float(ctx, btn, 9999, 1.0f) == RG_ERR_PROP);
|
||||||
|
CHECK(rg_get(ctx, btn, -1, &v) == RG_ERR_PROP);
|
||||||
|
|
||||||
|
memset(&v, 0, sizeof v);
|
||||||
|
v.type = RG_VAL_SIZE;
|
||||||
|
v.i = 99;
|
||||||
|
CHECK(rg_set(ctx, btn, RG_PROP_WIDTH, &v) == RG_ERR_TYPE);
|
||||||
|
v.i = RG_SIZE_FIXED;
|
||||||
|
v.f[0] = 200.0f;
|
||||||
|
CHECK(rg_set(ctx, btn, RG_PROP_WIDTH, &v) == RG_OK);
|
||||||
|
|
||||||
|
strcpy(buf, "Start");
|
||||||
|
CHECK(rg_set_string(ctx, lbl, RG_PROP_TEXT, buf) == RG_OK);
|
||||||
|
strcpy(buf, "Other");
|
||||||
|
CHECK(rg_get(ctx, lbl, RG_PROP_TEXT, &v) == RG_OK &&
|
||||||
|
strcmp(v.s, "Start") == 0);
|
||||||
|
|
||||||
|
CHECK(rg_set_string(ctx, lbl, RG_PROP_TEXT, v.s) == RG_OK);
|
||||||
|
CHECK(rg_get(ctx, lbl, RG_PROP_TEXT, &v) == RG_OK &&
|
||||||
|
strcmp(v.s, "Start") == 0);
|
||||||
|
|
||||||
|
CHECK(rg_set_string(ctx, lbl, RG_PROP_TEXT,
|
||||||
|
"A much longer replacement string") == RG_OK);
|
||||||
|
CHECK(rg_get(ctx, lbl, RG_PROP_TEXT, &v) == RG_OK &&
|
||||||
|
strcmp(v.s, "A much longer replacement string") == 0);
|
||||||
|
|
||||||
|
for (i = 0; i < RG_PROP_COUNT; i++) {
|
||||||
|
rg_value tmp;
|
||||||
|
memset(&tmp, 0, sizeof tmp);
|
||||||
|
tmp.type = rg_prop_type(i);
|
||||||
|
tmp.s = tmp.type == RG_VAL_STRING ? "x" : NULL;
|
||||||
|
if (tmp.type == RG_VAL_SIZE)
|
||||||
|
tmp.i = RG_SIZE_FIT;
|
||||||
|
CHECK(rg_set(ctx, btn, i, &tmp) == RG_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
rg_node_destroy(ctx, hbox);
|
||||||
|
rg_node_destroy(ctx, vbox);
|
||||||
|
rg_node_destroy(ctx, btn);
|
||||||
|
rg_node_destroy(ctx, lbl);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
testing_alloc ta = {0, 0};
|
||||||
|
rg_config cfg;
|
||||||
|
rg_context *ctx;
|
||||||
|
|
||||||
|
rg_config_init(&cfg);
|
||||||
|
testing_use_counting_allocator(&cfg, &ta);
|
||||||
|
cfg.log_level = RG_LOG_WARN;
|
||||||
|
|
||||||
|
ctx = rg_create(&cfg);
|
||||||
|
CHECK(ctx != NULL);
|
||||||
|
if (!ctx)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
test_tree(ctx);
|
||||||
|
test_handles(ctx);
|
||||||
|
test_props(ctx);
|
||||||
|
|
||||||
|
rg_destroy(ctx);
|
||||||
|
CHECK(ta.live == 0);
|
||||||
|
if (ta.live != 0)
|
||||||
|
printf("leak: %zu bytes still live\n", ta.live);
|
||||||
|
|
||||||
|
printf("nodes: %s, peak %zu bytes\n", g_failures ? "FAILED" : "ok", ta.peak);
|
||||||
|
return g_failures ? 1 : 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
|
||||||
|
#ifndef RG_TESTING_H
|
||||||
|
#define RG_TESTING_H
|
||||||
|
|
||||||
|
#include "regolith.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static int g_failures;
|
||||||
|
|
||||||
|
#define CHECK(cond) \
|
||||||
|
do { \
|
||||||
|
if (!(cond)) { \
|
||||||
|
printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
||||||
|
g_failures++; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
typedef struct testing_alloc {
|
||||||
|
size_t live;
|
||||||
|
size_t peak;
|
||||||
|
} testing_alloc;
|
||||||
|
|
||||||
|
static void ta_track(testing_alloc *t, size_t add, size_t sub) {
|
||||||
|
t->live = t->live + add - sub;
|
||||||
|
if (t->live > t->peak)
|
||||||
|
t->peak = t->live;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *ta_alloc(size_t size, void *user) {
|
||||||
|
void *p = malloc(size);
|
||||||
|
if (p)
|
||||||
|
ta_track(user, size, 0);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *ta_realloc(void *ptr, size_t old_size, size_t new_size,
|
||||||
|
void *user) {
|
||||||
|
void *p = realloc(ptr, new_size);
|
||||||
|
if (p)
|
||||||
|
ta_track(user, new_size, old_size);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ta_free(void *ptr, size_t size, void *user) {
|
||||||
|
ta_track(user, 0, size);
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void testing_use_counting_allocator(rg_config *cfg, testing_alloc *t) {
|
||||||
|
cfg->allocator.alloc = ta_alloc;
|
||||||
|
cfg->allocator.realloc = ta_realloc;
|
||||||
|
cfg->allocator.free = ta_free;
|
||||||
|
cfg->allocator.user = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in new issue
Block a user