Reworked property system completely.

Properties can now have values of different types, and they are registered with their name, either to a layout class or globally.
Layout classes are also registered with their name.
This commit is contained in:
Gwendolyn 2023-02-08 00:19:58 +01:00
parent 29b5bda326
commit d89ef83551
35 changed files with 1690 additions and 799 deletions

View file

@ -3,33 +3,36 @@ project(clay C)
set(CMAKE_C_STANDARD 17)
include(FetchContent)
# todo: use FetchContent instead https://cmake.org/cmake/help/latest/module/FetchContent.html#examples
find_package(PkgConfig)
pkg_check_modules(PC_CAIRO QUIET cairo)
find_path(CAIRO_INCLUDE_DIRS
NAMES cairo.h
HINTS ${PC_CAIRO_INCLUDEDIR}
${PC_CAIRO_INCLUDE_DIRS}
PATH_SUFFIXES cairo
FetchContent_Declare(
cairo
GIT_REPOSITORY https://gitlab.freedesktop.org/cairo/cairo.git
GIT_TAG c3b672634f0635af1ad0ffa8c15b34fc7c1035cf # 1.17.8
)
find_library(CAIRO_LIBRARIES
NAMES cairo
HINTS ${PC_CAIRO_LIBDIR}
${PC_CAIRO_LIBRARY_DIRS}
)
#FetchContent_MakeAvailable(cairo)
add_library(clay src/clay.c src/clay-base.c src/clay-context.c src/clay-layout.c src/clay-property.c src/clay-text.c
src/clay-flex.c src/clay-document.c src/clay-color.c)
add_library(clay
src/color.c
src/context.c
src/debug.c
src/document.c
src/flex.c
src/layout.c
src/property.c
src/render.c
src/text.c
)
target_include_directories(clay PUBLIC include)
target_include_directories(clay PRIVATE ${CAIRO_INCLUDE_DIRS})
target_link_libraries(clay PRIVATE ${CAIRO_LIBRARIES})
target_link_libraries(clay PRIVATE cairo)
target_compile_options(clay PRIVATE -Wall -Werror)
if(PROJECT_IS_TOP_LEVEL)
if (PROJECT_IS_TOP_LEVEL)
add_executable(clay-demo src/demo.c)
target_link_libraries(clay-demo PRIVATE clay)
endif()
endif ()

View file

@ -1,18 +0,0 @@
#ifndef CLAYOUT_CLAY_BASE_H
#define CLAYOUT_CLAY_BASE_H
typedef struct clay_ctx_t *clay_ctx;
typedef struct clay_t *clay;
clay_ctx clay_create_context(void);
void clay_destroy_context(clay_ctx);
void clay_set(clay, const char*, ...);
clay clay_clone(clay);
#endif //CLAYOUT_CLAY_BASE_H

View file

@ -1,40 +0,0 @@
#ifndef CLAYOUT_CLAY_FLEX_H
#define CLAYOUT_CLAY_FLEX_H
enum clay_flex_direction_e {
CLAY_FLEX_DIRECTION_ROW = 0,
CLAY_FLEX_DIRECTION_ROW_REVERSE = 1,
CLAY_FLEX_DIRECTION_COLUMN = 2,
CLAY_FLEX_DIRECTION_COLUMN_REVERSE = 3,
};
enum clay_flex_wrap_e {
CLAY_FLEX_WRAP_NO_WRAP = 0,
CLAY_FLEX_WRAP_WRAP = 1,
CLAY_FLEX_WRAP_WRAP_REVERSE = 2,
};
enum clay_flex_align_items_e {
CLAY_FLEX_ALIGN_ITEMS_START = 0,
CLAY_FLEX_ALIGN_ITEMS_END = 1,
CLAY_FLEX_ALIGN_ITEMS_CENTER = 2,
CLAY_FLEX_ALIGN_ITEMS_STRETCH = 3,
};
enum clay_flex_align_content_e {
CLAY_FLEX_ALIGN_CONTENT_START = 0,
CLAY_FLEX_ALIGN_CONTENT_END = 1,
CLAY_FLEX_ALIGN_CONTENT_CENTER = 2,
CLAY_FLEX_ALIGN_CONTENT_STRETCH = 3,
CLAY_FLEX_ALIGN_CONTENT_SPACE_BETWEEN = 4,
CLAY_FLEX_ALIGN_CONTENT_SPACE_AROUND = 5,
CLAY_FLEX_ALIGN_CONTENT_SPACE_EVENLY = 6,
};
clay clay_create_flex(clay_ctx);
void clay_flex_register_props(clay_ctx ctx); // todo: should be private
#endif //CLAYOUT_CLAY_FLEX_H

View file

@ -1,23 +0,0 @@
#ifndef CLAYOUT_CLAY_PROPERTIES_H
#define CLAYOUT_CLAY_PROPERTIES_H
#define CLAY_PROPERTY_TEXT 1
#define CLAY_PROPERTY_BG_COLOR 2
#define CLAY_PROPERTY_WIDTH 3
#define CLAY_PROPERTY_FLEX_GROW 4
#define CLAY_PROPERTY_FLEX_SHRINK 5
#define CLAY_PROPERTY_TEXT_ALIGN 6
#define CLAY_PROPERTY_TEXT_VERTICAL_ALIGN 7
#define CLAY_PROPERTY_FLEX_DIRECTION 8
#define CLAY_PROPERTY_FLEX_WRAP 9
#define CLAY_PROPERTY_FLEX_ALIGN_ITEMS 10
#define CLAY_PROPERTY_FLEX_ALIGN_CONTENT 11
#define CLAY_PROPERTY_FLEX_GAP 12
#define CLAY_PROPERTY_PADDING 13
#define CLAY_PROPERTY_CONTENTS 14
#define CLAY_PROPERTY_HEIGHT 15
#define CLAY_PROPERTY_CONTENT 16
#endif

View file

@ -1,20 +0,0 @@
#ifndef CLAYOUT_CLAY_TEXT_H
#define CLAYOUT_CLAY_TEXT_H
enum clay_text_align_e {
CLAY_TEXT_ALIGN_LEFT = 0,
CLAY_TEXT_ALIGN_CENTER = 1,
CLAY_TEXT_ALIGN_RIGHT = 2,
};
enum clay_text_vertical_align_e {
CLAY_TEXT_VERTICAL_ALIGN_TOP = 0,
CLAY_TEXT_VERTICAL_ALIGN_MIDDLE = 1,
CLAY_TEXT_VERTICAL_ALIGN_BOTTOM = 2,
};
clay clay_create_text(clay_ctx);
void clay_text_register_props(clay_ctx ctx); // todo: should be private
#endif //CLAYOUT_CLAY_TEXT_H

View file

@ -2,19 +2,110 @@
#define CLAYOUT_CLAY_H
#include "clay-base.h"
#include "clay-properties.h"
#include "clay-flex.h"
#include "clay-text.h"
#include <stdarg.h>
#include <stdint.h>
clay clay_create_document(clay_ctx);
typedef struct clay_ctx_t *clay_ctx;
typedef struct clay_t *clay;
clay_ctx clay_create_context(void);
void clay_destroy_context(clay_ctx);
clay clay_create(clay_ctx ctx, const char *layout_class_name);
clay clay_clone(clay);
clay clay_clone_recursive(clay);
void clay_layout_set_property(clay layout, const char *prop_name, int num_values, ...);
/*
* A layout can only be child of one parent layout.
* If a layout that is already a child of a parent layout is appended as a child to another layout,
* it is automatically removed from its previous parent.
*/
void clay_append_child(clay parent, clay child);
void clay_layout_destroy(clay layout);
void clay_render_to_png(clay, const char *);
void clay_debug_layout(clay doc);
void clay_print_layout_tree(clay doc);
void clay_document_register_props(clay_ctx ctx); // todo: should be private
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
} clay_color;
enum clay_prop_val_in_type {
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
TYPE_COLOR,
};
struct clay_prop_val_in {
enum clay_prop_val_in_type type;
union {
int value_int;
double value_double;
const char *value_string;
clay_color value_color;
};
};
static inline struct clay_prop_val_in clay_make_prop_val(enum clay_prop_val_in_type type, ...) {
struct clay_prop_val_in result = {.type=type};
va_list args;
va_start(args, type);
switch (type) {
case TYPE_INT:
result.value_int = va_arg(args, int);
break;
case TYPE_DOUBLE:
result.value_double = va_arg(args, double);
break;
case TYPE_STRING:
result.value_string = va_arg(args, const char *);
break;
case TYPE_COLOR:
result.value_color = va_arg(args, clay_color);
break;
}
va_end(args);
return result;
}
#define _clay_value(v) _Generic(v, \
char *: clay_make_prop_val(TYPE_STRING, v), \
const char *: clay_make_prop_val(TYPE_STRING, v), \
int: clay_make_prop_val(TYPE_INT, v), \
double: clay_make_prop_val(TYPE_DOUBLE, v), \
clay_color: clay_make_prop_val(TYPE_COLOR, v) \
)
#define _clay_set1(l, n, v1) clay_layout_set_property(l, n, 1, _clay_value(v1))
#define _clay_set2(l, n, v1, v2) clay_layout_set_property(l, n, 2, _clay_value(v1), _clay_value(v2))
#define _clay_set3(l, n, v1, v2, v3) clay_layout_set_property(l, n, 3, _clay_value(v1), _clay_value(v2), _clay_value(v3))
#define _clay_set4(l, n, v1, v2, v3, v4) clay_layout_set_property(l, n, 4, _clay_value(v1), _clay_value(v2), _clay_value(v3), _clay_value(v4))
#define _clay_macro_overload(_1, _2, _3, _4, NAME, ...) NAME
#define clay_set(l, n, ...) _clay_macro_overload(__VA_ARGS__, _clay_set4, _clay_set3, _clay_set2, _clay_set1)(l, n, __VA_ARGS__)
///////////////////////////////////////////////////////////////////////
void clay_document_register(clay_ctx ctx); // todo: should be private
void clay_flex_register(clay_ctx ctx); // todo: should be private
void clay_text_register(clay_ctx ctx); // todo: should be private
#endif //CLAYOUT_CLAY_H

View file

@ -1,7 +0,0 @@
#include <malloc.h>
#include <string.h>
#include "clay-base.h"
#include <assert.h>

View file

@ -1,15 +0,0 @@
#ifndef CLAY_CLAY_COLOR_H
#define CLAY_CLAY_COLOR_H
#include <stdint.h>
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} clay_color;
clay_color clay_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
#endif //CLAY_CLAY_COLOR_H

View file

@ -1,125 +0,0 @@
#include <clay.h>
#include <stddef.h>
#include <malloc.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include "clay-layout.h"
#include "clay-context.h"
// todo: thread safety? maybe as an option CLAY_ENABLE_THREADSAFE
struct clay_ctx_t {
clay *layouts_ptrs;
size_t layouts_length;
size_t layouts_size;
clay_property_desc_s *property_descs;
size_t property_descs_length;
size_t property_descs_size;
int property_tag_cnt;
};
clay_ctx clay_create_context(void) {
clay_ctx ctx = malloc(sizeof *ctx);
assert(ctx != NULL);
ctx->layouts_size = 16;
ctx->layouts_length = 0;
ctx->layouts_ptrs = malloc(sizeof(*ctx->layouts_ptrs) * ctx->layouts_size);
assert(ctx->layouts_ptrs != NULL);
ctx->property_tag_cnt = 0;
ctx->property_descs_size = 16;
ctx->property_descs_length = 0;
ctx->property_descs = malloc(sizeof(*ctx->property_descs) * ctx->property_descs_size);
assert(ctx->property_descs != NULL);
clay_text_register_props(ctx);
clay_flex_register_props(ctx);
clay_document_register_props(ctx);
return ctx;
}
void clay_destroy_context(clay_ctx ctx) {
// TODO: free everything owned by ctx
for (size_t i = 0; i < ctx->layouts_length; ++i) {
clay layout = ctx->layouts_ptrs[i];
clay_cleanup_layout(layout);
free(layout);
}
free(ctx->layouts_ptrs);
free(ctx->property_descs);
free(ctx);
}
void clay_ctx_register_layout(clay_ctx ctx, clay layout) {
assert(ctx->layouts_length <= ctx->layouts_size);
if (ctx->layouts_length == ctx->layouts_size) {
size_t new_size = ctx->layouts_size * 2;
ctx->layouts_ptrs = realloc(ctx->layouts_ptrs, sizeof(*ctx->layouts_ptrs) * new_size);
assert(ctx->layouts_ptrs != NULL);
ctx->layouts_size = new_size;
}
ctx->layouts_length += 1;
ctx->layouts_ptrs[ctx->layouts_length - 1] = layout;
}
void clay_ctx_unregister_layout(clay_ctx ctx, clay layout) {
for (size_t i = 0; i < ctx->layouts_length; ++i) {
if (ctx->layouts_ptrs[i] == layout) {
ctx->layouts_ptrs[i] = ctx->layouts_ptrs[ctx->layouts_length - 1];
ctx->layouts_length -= 1;
}
}
}
int clay_ctx_register_property(clay_ctx ctx, const char *name, clay_property_type type) {
assert (!clay_ctx_has_property(ctx, name));
int tag = ++ctx->property_tag_cnt;
assert(ctx->property_descs_length <= ctx->property_descs_size);
if (ctx->property_descs_length == ctx->property_descs_size) {
size_t new_size = ctx->property_descs_size * 2;
ctx->property_descs = realloc(ctx->property_descs, sizeof(*ctx->property_descs) * new_size);
assert(ctx->property_descs != NULL);
ctx->property_descs_size = new_size;
}
ctx->property_descs_length += 1;
ctx->property_descs[ctx->property_descs_length - 1] = (clay_property_desc_s) {
.type = type,
.tag = tag,
.name = name
};
return tag;
}
void clay_ctx_unregister_property(clay_ctx ctx, int tag) {
for (size_t i = 0; i < ctx->property_descs_length; ++i) {
if (ctx->property_descs[i].tag == tag) {
ctx->property_descs[i] = ctx->property_descs[ctx->property_descs_length - 1];
ctx->property_descs_length -= 1;
return;
}
}
}
bool clay_ctx_has_property(clay_ctx ctx, const char *name) {
for (size_t i = 0; i < ctx->property_descs_length; ++i) {
if (strcmp(ctx->property_descs[i].name, name) == 0) {
return true;
}
}
return false;
}
clay_property_desc clay_ctx_get_property_desc(clay_ctx ctx, const char *name) {
for (size_t i = 0; i < ctx->property_descs_length; ++i) {
if (strcmp(ctx->property_descs[i].name, name) == 0) {
return &ctx->property_descs[i];
}
}
return NULL;
}

View file

@ -1,23 +0,0 @@
#ifndef CLAY_CLAY_CONTEXT_H
#define CLAY_CLAY_CONTEXT_H
#include "clay-property.h"
#include <stdbool.h>
typedef struct {
const char * name;
int tag;
clay_property_type type;
} clay_property_desc_s, *clay_property_desc;
// TODO: properties should not keep track of the type and allow any type, or something?
void clay_ctx_register_layout(clay_ctx ctx, clay layout);
void clay_ctx_unregister_layout(clay_ctx ctx, clay layout);
int clay_ctx_register_property(clay_ctx ctx, const char * name, clay_property_type type);
void clay_ctx_unregister_property(clay_ctx ctx, int tag);
bool clay_ctx_has_property(clay_ctx ctx, const char *name);
clay_property_desc clay_ctx_get_property_desc(clay_ctx ctx, const char * name);
#endif //CLAY_CLAY_CONTEXT_H

View file

@ -1,70 +0,0 @@
#include "clay.h"
#include "clay-layout.h"
#include "clay-context.h"
#include "clay-color.h"
static void cleanup_document(clay layout);
static void init_document(clay layout);
static void debug_document(clay layout);
static struct layout_class layout_class_document = {
.init = &init_document,
.cleanup = &cleanup_document,
.debug = &debug_document,
};
static void init_document(clay layout) {
// todo: anything?
}
static void cleanup_document(clay layout) {
// todo: anything?
}
static void debug_document(clay layout) {
printf("document:\n");
clay_property_value width_prop = clay_get_prop(layout, "width");
clay_property_value height_prop = clay_get_prop(layout, "height");
clay_property_value content_prop = clay_get_prop(layout, "content");
clay_property_value bgcolor_prop = clay_get_prop(layout, "bg-color");
if (width_prop->type == CLAY_PROPERTY_INT) {
printf(" width: %d\n", width_prop->int_val);
}
if (height_prop->type == CLAY_PROPERTY_INT) {
printf(" height: %d\n", height_prop->int_val);
}
if (bgcolor_prop->type == CLAY_PROPERTY_POINTER) {
clay_color *color = (clay_color*)bgcolor_prop->pointer_val;
printf(" bg-color: (%d, %d, %d, %d)\n", color->r, color->g, color->b, color->a);
}
printf("\n");
if (content_prop->type == CLAY_PROPERTY_POINTER && content_prop->pointer_val != NULL) {
clay content = (clay) content_prop->pointer_val;
content->class.debug(content);
}
}
clay clay_create_document(clay_ctx ctx) {
return clay_create_layout(ctx, layout_class_document);
}
void clay_render_to_png(clay doc, const char *path) {
// todo
}
void clay_debug_layout(clay doc) {
doc->class.debug(doc);
}
void clay_document_register_props(clay_ctx ctx) {
clay_ctx_register_property(ctx, "width", CLAY_PROPERTY_INT);
clay_ctx_register_property(ctx, "height", CLAY_PROPERTY_INT);
clay_ctx_register_property(ctx, "content", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "bg-color", CLAY_PROPERTY_POINTER);
}

View file

@ -1,82 +0,0 @@
#include "clay.h"
#include "clay-layout.h"
#include "clay-context.h"
static void cleanup_flex(clay layout);
static void init_flex(clay layout);
static void debug_flex(clay layout);
static struct layout_class layout_class_flex = {
.init = &init_flex,
.cleanup = &cleanup_flex,
.debug = &debug_flex,
};
static void init_flex(clay layout) {
// todo: anything?
}
static void cleanup_flex(clay layout) {
// todo: anything?
}
static void debug_flex(clay layout) {
printf("flex:\n");
clay_property_value direction_prop = clay_get_prop(layout, "direction");
clay_property_value wrap_prop = clay_get_prop(layout, "wrap");
clay_property_value align_items_prop = clay_get_prop(layout, "align-items");
clay_property_value align_content_prop = clay_get_prop(layout, "align-content");
clay_property_value gap_prop = clay_get_prop(layout, "gap");
clay_property_value padding_prop = clay_get_prop(layout, "padding");
clay_property_value contents_prop = clay_get_prop(layout, "contents");
if (direction_prop->type == CLAY_PROPERTY_POINTER) {
printf(" direction: %s\n", (char*)direction_prop->pointer_val);
}
if (wrap_prop->type == CLAY_PROPERTY_POINTER) {
printf(" wrap: %s\n", (char*)wrap_prop->pointer_val);
}
if (align_items_prop->type == CLAY_PROPERTY_POINTER) {
printf(" align-items: %s\n", (char*)align_items_prop->pointer_val);
}
if (align_content_prop->type == CLAY_PROPERTY_POINTER) {
printf(" align-content: %s\n", (char*)align_content_prop->pointer_val);
}
if (gap_prop->type == CLAY_PROPERTY_INT) {
printf(" gap: %d\n", gap_prop->int_val);
}
if (padding_prop->type == CLAY_PROPERTY_INT) {
printf(" padding: %d\n", padding_prop->int_val);
}
printf("\n");
if (contents_prop->type == CLAY_PROPERTY_POINTER) {
clay* ptr = (clay*)contents_prop->pointer_val;
while(*ptr != NULL) {
clay content = *ptr;
content->class.debug(content);
ptr += 1;
}
}
}
clay clay_create_flex(clay_ctx ctx) {
return clay_create_layout(ctx, layout_class_flex);
}
void clay_flex_register_props(clay_ctx ctx) {
clay_ctx_register_property(ctx, "direction", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "wrap", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "align-items", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "align-content", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "gap", CLAY_PROPERTY_INT);
clay_ctx_register_property(ctx, "padding", CLAY_PROPERTY_INT);
clay_ctx_register_property(ctx, "contents", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "flex:grow", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "flex:shrink", CLAY_PROPERTY_POINTER);
}

View file

@ -1,78 +0,0 @@
#include <malloc.h>
#include <stdarg.h>
#include <assert.h>
#include "clay-layout.h"
#include "clay-context.h"
#include "clay-property.h"
clay clay_create_layout(clay_ctx ctx, struct layout_class class) {
clay layout = malloc(sizeof(*layout));
assert(layout != NULL);
layout->properties = clay_propset_create();
layout->class = class;
layout->ctx = ctx;
if (layout->class.init != NULL) {
layout->class.init(layout);
}
clay_ctx_register_layout(ctx, layout);
return layout;
}
void clay_destroy_layout(clay layout) {
clay_ctx_unregister_layout(layout->ctx, layout);
clay_cleanup_layout(layout);
free(layout);
}
void clay_cleanup_layout(clay layout) {
if (layout->class.cleanup != NULL) {
layout->class.cleanup(layout);
}
clay_propset_destroy(layout->properties);
}
clay clay_clone(clay layout) {
clay cloned = malloc(sizeof(*cloned));
cloned->class = layout->class;
cloned->ctx = layout->ctx;
cloned->properties = clay_propset_clone(layout->properties);
return cloned;
}
clay_property_value clay_get_prop(clay layout, const char *prop) {
clay_ctx ctx = layout->ctx;
clay_property_desc desc = clay_ctx_get_property_desc(ctx, prop);
assert(desc != NULL);
assert(desc->type != CLAY_PROPERTY_NOT_SET);
return clay_property_get_by_tag(layout->properties, desc->tag);
}
void clay_set(clay layout, const char *prop, ...) {
// TODO: use a macro so that a property can have values of different types
// the property registry should not keep the type then
// also the property registry can have some kind of alias mechanism for translating strings to enums or something
clay_ctx ctx = layout->ctx;
clay_property_desc desc = clay_ctx_get_property_desc(ctx, prop);
assert(desc != NULL);
assert(desc->type != CLAY_PROPERTY_NOT_SET);
struct clay_property_value value;
value.type = desc->type;
va_list args;
va_start(args, prop);
switch (desc->type) {
case CLAY_PROPERTY_NOT_SET:
break;
case CLAY_PROPERTY_INT:
value.int_val = va_arg(args, int);
break;
case CLAY_PROPERTY_FLOAT:
value.float_val = va_arg(args, double);
break;
case CLAY_PROPERTY_POINTER:
value.pointer_val = va_arg(args, void*);
break;
}
va_end(args);
clay_property_set_by_tag(layout->properties, desc->tag, value);
}

View file

@ -1,33 +0,0 @@
#ifndef CLAY_CLAY_LAYOUT_H
#define CLAY_CLAY_LAYOUT_H
#include "clay.h"
#include "clay-property.h"
typedef void (*init_fn)(clay);
typedef void (*cleanup_fn)(clay);
typedef void (*debug_fn)(clay);
struct layout_class {
init_fn init;
cleanup_fn cleanup;
debug_fn debug;
};
struct clay_t {
struct layout_class class;
clay_property_set properties;
clay_ctx ctx;
};
clay clay_create_layout(clay_ctx ctx, struct layout_class class);
void clay_destroy_layout(clay layout);
void clay_cleanup_layout(clay layout);
clay_property_value clay_get_prop(clay layout, const char *prop);
#endif //CLAY_CLAY_LAYOUT_H

92
src/clay-list.h Normal file
View file

@ -0,0 +1,92 @@
#ifndef CLAY_CLAY_LIST_H
#define CLAY_CLAY_LIST_H
#ifndef __GNUC__
#error "this requires GCC" // todo: make clang-compatible if possible
#endif
#include <stddef.h>
#include <malloc.h>
#include <assert.h>
#include <memory.h>
#ifndef CLAY_LIST_DEFAULT_INITIAL_SIZE
# define CLAY_LIST_DEFAULT_INITIAL_SIZE 16
#endif
#define CLAY_LIST_TYPE(list$name, list$element_type) \
typedef struct { \
list$element_type *elements; \
size_t length; \
size_t size;\
} list$name ## _s, * list$name; \
typedef const list$name ## _s * list$name ## _const;
#define CLAY_LIST_FOREACH(foreach$list, foreach$elptr, foreach$idx) \
for (int list_foreach$next = 1; list_foreach$next; (list_foreach$next = 0)) \
for (size_t foreach$idx = 0; list_foreach$next && foreach$idx < (foreach$list)->length; ++foreach$idx) \
for (typeof((foreach$list)->elements) foreach$elptr = &(foreach$list)->elements[foreach$idx]; list_foreach$next;) \
for (; !(list_foreach$next = 0); ({list_foreach$next = 1;break;}))
#define clay_list_create_1(list_create_1$type) clay_list_create_2(list_create_1$type, CLAY_LIST_DEFAULT_INITIAL_SIZE)
#define clay_list_create_2(list_create_2$type, list_create_2$initial_size) ({ \
assert((list_create_2$initial_size) > 0); \
list_create_2$type list_create_2$list = malloc(sizeof(*list_create_2$list)); \
assert(list_create_2$list != NULL); \
clay_list_init(list_create_2$list, (list_create_2$initial_size)); \
list_create_2$list; \
})
#define clay_list_create(...) CMOBALL(clay_list_create, __VA_ARGS__)
#define clay_list_init(list_init$list, list_init$initial_size) ({ \
(list_init$list)->length = 0; \
(list_init$list)->size = (list_init$initial_size); \
(list_init$list)->elements = malloc(sizeof(*(list_init$list)->elements) * (list_init$list)->size); \
assert((list_init$list)->elements != NULL); \
})
#define clay_list_append(list_append$list, list_append$el) ({ \
assert((list_append$list)->length <= (list_append$list)->size); \
if ((list_append$list)->length == (list_append$list)->size) { \
size_t list_append$new_size = (list_append$list)->size * 2; \
(list_append$list)->elements = realloc((list_append$list)->elements, sizeof(*(list_append$list)->elements) * list_append$new_size); \
(list_append$list)->size = list_append$new_size; \
assert((list_append$list)->elements != NULL); \
} \
(list_append$list)->length += 1; \
(list_append$list)->elements[(list_append$list)->length - 1] = (list_append$el); \
})
#define clay_list_remove(list_remove$list, list_remove$idx) ({ \
assert((list_remove$idx) < (list_remove$list)->length); \
if ((list_remove$idx) < (list_remove$list)->length - 1) { \
memmove((list_remove$list)->elements + (list_remove$idx), (list_remove$list)->elements + (list_remove$idx) + 1, ((list_remove$list)->length - (list_remove$idx) - 1) * sizeof(*(list_remove$list)->elements)); \
} \
(list_remove$list)->length -= 1; \
})
#define clay_list_destroy(list_destroy$list) ({ \
clay_list_empty(list_destroy$list); \
free(list_destroy$list); \
})
#define clay_list_empty(list_empty$list) ({ \
free((list_empty$list)->elements); \
(list_empty$list)->elements = NULL; \
})
#define clay_list_clone(list_clone$list) ({ \
typeof(list_clone$list) list_clone$new = clay_list_create(typeof(list_clone$list), (list_clone$list)->length); \
memcpy(list_clone$new->elements, (list_clone$list)->elements, (list_clone$list)->length); \
list_clone$new->length = (list_clone$list)->length; \
list_clone$new; \
})
#endif //CLAY_CLAY_LIST_H

167
src/clay-map.h Normal file
View file

@ -0,0 +1,167 @@
#ifndef CLAY_CLAY_MAP_H
#define CLAY_CLAY_MAP_H
#ifndef __GNUC__
#error "this requires GCC" // todo: make clang-compatible if possible
#endif
#include <stdbool.h>
#include <stddef.h>
#include "clay-list.h"
#include "cmoball.h"
#ifndef CLAY_MAP_DEFAULT_INITIAL_SIZE
# define CLAY_MAP_DEFAULT_INITIAL_SIZE 16
#endif
#define CLAY_MAP_TYPE(map_type$name, map_type$key_type, map_type$value_type) \
typedef struct { \
map_type$key_type key; \
map_type$value_type value; \
} map_type$name ## _entry_s, *map_type$name ## _entry; \
typedef int (* map_type$name ## _compare_fn)(map_type$key_type, map_type$key_type); \
typedef struct { \
map_type$name ## _entry_s *elements; \
size_t length; \
size_t size; \
map_type$name ## _compare_fn compare_fn; \
} map_type$name ## _s, *map_type$name; \
typedef const map_type$name ## _s * map_type$name ## _const;
#define CLAY_STRING_MAP_TYPE(map_type$name, map_type$value_type) CLAY_MAP_TYPE(map_type$name, const char *, map_type$value_type)
#define CLAY_MAP_FOREACH(map_foreach$map, map_foreach$key, map_foreach$valueptr) \
for (int map_foreach$next = 1; map_foreach$next; (map_foreach$next = 0)) \
for (size_t map_foreach$i = 0; map_foreach$next && map_foreach$i < (map_foreach$map)->length; ++map_foreach$i) \
for (typeof((map_foreach$map)->elements) map_foreach$el = &(map_foreach$map)->elements[map_foreach$i]; map_foreach$next; ({ break; })) \
for (typeof(map_foreach$el->key) map_foreach$key = map_foreach$el->key; map_foreach$next; ({ break; })) \
for (typeof(map_foreach$el->value) *map_foreach$valueptr = &map_foreach$el->value; map_foreach$next;) \
for (; !(map_foreach$next = 0); ({map_foreach$next = 1;break;}))
#define clay_map_init(map$, map$initial_size) clay_list_init((map$), (map$initial_size))
// note: map$type is never in parantheses in the substitution, because it is a type and not an expression!
#define clay_map_create_1(map$type) clay_map_create_default_cmp(map$type, CLAY_MAP_DEFAULT_INITIAL_SIZE)
#define clay_map_create_2(map$type, map$size_or_cmp) _Generic((map$size_or_cmp), int: clay_map_create_default_cmp(map$type, (size_t)(map$size_or_cmp)), default: clay_map_create_3(map$type, CLAY_MAP_DEFAULT_INITIAL_SIZE, (void*)(map$size_or_cmp)))
#define clay_map_create_default_cmp(map$type, map$size) clay_map_create_3(map$type, map$size, ({ map$type map$dummy; _Generic(map$dummy->elements->key, const char *: strcmp, default: NULL);}))
#define clay_map_create_3(map$type, map$size, map$cmp) ({ \
assert((map$size) > 0); \
map$type map$map = malloc(sizeof(*map$map)); \
clay_map_init(map$map, (map$size)); \
map$map->compare_fn = map$cmp; \
map$map; \
})
#define clay_map_create(...) CMOBALL(clay_map_create, __VA_ARGS__)
CLAY_MAP_TYPE(some_map, int, int)
CLAY_STRING_MAP_TYPE(other_map, int)
#define clay_map_empty(map$) free((map$)->elements)
#define clay_map_destroy(map$) ({ \
clay_map_empty(map$); \
free(map$); \
})
#define clay_map_set(map_set$map, map_set$key, map_set$value) ({ \
bool map_set$found = false; \
CLAY_LIST_FOREACH((map_set$map), map_set$el, map_set$_) { \
if (((map_set$map)->compare_fn == NULL && map_set$el->key == map_set$key) || \
((map_set$map)->compare_fn != NULL && (map_set$map)->compare_fn(map_set$el->key, (map_set$key)) == 0)) { \
map_set$el->value = (map_set$value); \
map_set$found = true; \
break; \
} \
} \
if (!map_set$found) { \
typeof(*(map_set$map)->elements) map_set$el = {.key = (map_set$key), .value = (map_set$value)}; \
clay_list_append((map_set$map), map_set$el); \
} \
})
#define clay_map_get(map_get$map, map_get$key, map_get$value) ({ \
bool map_get$found = false; \
CLAY_LIST_FOREACH((map_get$map), map_get$el, map_get$_) { \
if (((map_get$map)->compare_fn == NULL && map_get$el->key == (map_get$key)) || \
((map_get$map)->compare_fn != NULL && (map_get$map)->compare_fn(map_get$el->key, (map_get$key)) == 0)) { \
*(map_get$value) = map_get$el->value; \
map_get$found = true; \
break; \
} \
} \
map_get$found; \
})
#define clay_map_get_ptr(map_get$map, map_get$key, map_get$value) ({ \
bool map_get$found = false; \
CLAY_LIST_FOREACH((map_get$map), map_get$el, map_get$_) { \
if (((map_get$map)->compare_fn == NULL && map_get$el->key == (map_get$key)) || \
((map_get$map)->compare_fn != NULL && (map_get$map)->compare_fn(map_get$el->key, (map_get$key)) == 0)) { \
*(map_get$value) = &map_get$el->value; \
map_get$found = true; \
break; \
} \
} \
map_get$found; \
})
#define clay_map_get_default(map_get$map, map_get$key, map_get$default) ({ \
typeof((map_get$map)->elements->value) map_get$value = map_get$default; \
CLAY_LIST_FOREACH((map_get$map), map_get$el, map_get$_) { \
if (((map_get$map)->compare_fn == NULL && map_get$el->key == (map_get$key)) || \
((map_get$map)->compare_fn != NULL && (map_get$map)->compare_fn(map_get$el->key, (map_get$key)) == 0)) { \
map_get$value = map_get$el->value; \
break; \
} \
} \
map_get$value; \
})
#define clay_map_has_key(map_has_key$map, map_has_key$key) ({ \
bool map_has_key$found = false; \
CLAY_LIST_FOREACH((map_has_key$map), map_has_key$el, map_has_key$) { \
if (((map_has_key$map)->compare_fn == NULL && map_has_key$el->key == (map_has_key$key)) || \
((map_has_key$map)->compare_fn != NULL && (map_has_key$map)->compare_fn(map_has_key$el->key, (map_has_key$key)) == 0)) { \
map_has_key$found = true; \
break; \
} \
} \
map_has_key$found; \
})
#define clay_map_remove(map_remove$map, map_remove$key) ({ \
bool map_remove$found = false; \
CLAY_LIST_FOREACH((map_remove$map), map_remove$el, map_remove$idx) { \
if (((map_remove$map)->compare_fn == NULL && map_remove$el->key == (map_remove$key)) || \
((map_remove$map)->compare_fn != NULL && (map_remove$map)->compare_fn(map_remove$el->key, (map_remove$key)) == 0)) { \
clay_list_remove((map_remove$map), map_remove$idx); \
map_remove$found = true; \
} \
} \
map_remove$found; \
})
#define clay_map_clone(map_clone$map) ({ \
typeof(map_clone$map) map_clone$map_new = clay_map_create(typeof(map_clone$map), (map_clone$map)->length); \
memcpy(map_clone$map_new->elements, (map_clone$map)->elements, (map_clone$map)->length * sizeof(*map_clone$map_new->elements)); \
map_clone$map_new->length = (map_clone$map)->length; \
map_clone$map_new->compare_fn = (map_clone$map)->compare_fn; \
map_clone$map_new; \
})
#endif //CLAY_CLAY_MAP_H

View file

@ -1,87 +0,0 @@
#include <string.h>
#include "clay-property.h"
struct clay_property {
int tag;
struct clay_property_value value;
};
struct clay_property_set_t {
struct clay_property *props;
size_t length;
size_t size;
};
static struct clay_property_value empty_property_value = {
.type = CLAY_PROPERTY_NOT_SET,
};
clay_property_set clay_propset_create(void) {
clay_property_set propset = malloc(sizeof(*propset));
assert(propset != NULL);
propset->length = 0;
propset->size = 16;
propset->props = malloc(sizeof(*propset->props) * 16);
assert(propset->props != NULL);
return propset;
}
clay_property_set clay_propset_clone(clay_property_set propset) {
clay_property_set cloned = malloc(sizeof(*cloned));
assert(propset != NULL);
cloned->length = propset->length;
cloned->size = propset->size;
cloned->props = malloc(sizeof(*cloned->props) * cloned->size);
memcpy(cloned->props, propset->props, sizeof(*cloned->props) * cloned->size);
return cloned;
}
void clay_propset_destroy(clay_property_set propset) {
free(propset->props);
free(propset);
}
clay_property_value clay_property_get_by_tag(clay_property_set propset, int tag) {
for (size_t i = 0; i < propset->length; ++i) {
struct clay_property *prop = &propset->props[i];
if (prop->tag == tag) {
return &prop->value;
}
}
return &empty_property_value;
}
void clay_property_set_by_tag(clay_property_set propset, int tag, struct clay_property_value value) {
struct clay_property *prop = NULL;
for (size_t i = 0; i < propset->length; ++i) {
if (propset->props[i].tag == tag) {
prop = &propset->props[i];
break;
}
}
if (prop == NULL) {
assert(propset->length <= propset->size);
if (propset->length == propset->size) {
size_t new_size = propset->size * 2;
propset->props = realloc(propset->props, sizeof(*propset->props) * new_size);
assert(propset->props != NULL);
propset->size = new_size;
}
propset->length += 1;
prop = &propset->props[propset->length - 1];
}
prop->tag = tag;
prop->value = value;
}
void clay_property_delete_by_tag(clay_property_set propset, int tag) {
for (size_t i = 0; i < propset->length; ++i) {
struct clay_property *prop = &propset->props[i];
if (prop->tag == tag) {
prop->value = empty_property_value;
}
}
}

View file

@ -1,41 +0,0 @@
#ifndef CLAY_CLAY_PROPERTY_H
#define CLAY_CLAY_PROPERTY_H
#include <stddef.h>
#include <assert.h>
#include <malloc.h>
typedef enum {
CLAY_PROPERTY_NOT_SET,
CLAY_PROPERTY_INT,
CLAY_PROPERTY_FLOAT,
CLAY_PROPERTY_POINTER,
} clay_property_type;
struct clay_property_value {
clay_property_type type;
union {
int int_val;
double float_val;
void *pointer_val;
};
};
typedef const struct clay_property_value *clay_property_value;
typedef struct clay_property_set_t *clay_property_set;
clay_property_set clay_propset_create(void);
clay_property_set clay_propset_clone(clay_property_set);
void clay_propset_destroy(clay_property_set propset);
clay_property_value clay_property_get_by_tag(clay_property_set propset, int tag);
void clay_property_set_by_tag(clay_property_set propset, int tag, struct clay_property_value value);
void clay_property_delete_by_tag(clay_property_set propset, int tag);
#endif //CLAY_CLAY_PROPERTY_H

View file

@ -1,67 +0,0 @@
#include "clay.h"
#include "clay-layout.h"
#include "clay-context.h"
#include "clay-color.h"
static void cleanup_text(clay layout);
static void init_text(clay layout);
static void debug_text(clay layout);
static struct layout_class layout_class_text = {
.cleanup = &cleanup_text,
.init = &init_text,
.debug = &debug_text,
};
static void init_text(clay layout) {
// todo: anything?
}
static void cleanup_text(clay layout) {
// todo: anything?
}
static void debug_text(clay layout) {
printf("text:\n");
clay_property_value content_prop = clay_get_prop(layout, "content");
clay_property_value bgcolor_prop = clay_get_prop(layout, "bg-color");
clay_property_value width_prop = clay_get_prop(layout, "width");
clay_property_value flex_grow_prop = clay_get_prop(layout, "flex:grow");
clay_property_value flex_shrink_prop = clay_get_prop(layout, "flex:shrink");
clay_property_value align_prop = clay_get_prop(layout, "align");
clay_property_value vertical_align_prop = clay_get_prop(layout, "vertical-align");
if (content_prop->type == CLAY_PROPERTY_POINTER) {
printf(" content: %s\n", (char*)content_prop->pointer_val);
}
if (bgcolor_prop->type == CLAY_PROPERTY_POINTER) {
clay_color *color = (clay_color*)bgcolor_prop->pointer_val;
printf(" bg-color: (%d, %d, %d, %d)\n", color->r, color->g, color->b, color->a);
}
if (width_prop->type == CLAY_PROPERTY_INT) {
printf(" width: %d\n", width_prop->int_val);
}
if (flex_grow_prop->type == CLAY_PROPERTY_INT) {
printf(" flex-grow: %d\n", flex_grow_prop->int_val);
}
if (flex_shrink_prop->type == CLAY_PROPERTY_INT) {
printf(" flex-shrink: %d\n", flex_shrink_prop->int_val);
}
if (align_prop->type == CLAY_PROPERTY_POINTER) {
printf(" align: %s\n", (char*)align_prop->pointer_val);
}
if (vertical_align_prop->type == CLAY_PROPERTY_POINTER) {
printf(" vertical-align: %s\n", (char*)vertical_align_prop->pointer_val);
}
}
clay clay_create_text(clay_ctx ctx) {
return clay_create_layout(ctx, layout_class_text);
}
void clay_text_register_props(clay_ctx ctx) {
clay_ctx_register_property(ctx, "align", CLAY_PROPERTY_POINTER);
clay_ctx_register_property(ctx, "vertical-align", CLAY_PROPERTY_POINTER);
}

View file

@ -1,20 +0,0 @@
#include "clay.h"
#include <cairo/cairo.h>
#include <stdio.h>
void hello(void) {
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 240, 80);
cairo_t *cr = cairo_create (surface);
cairo_select_font_face (cr, "sans-serif", CAIRO_FONT_SLANT_ITALIC, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size (cr, 10.0);
cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
cairo_move_to (cr, 10.0, 50.0);
cairo_show_text (cr, "Hello, world");
cairo_destroy (cr);
cairo_surface_write_to_png (surface, "hello.png");
cairo_surface_destroy (surface);
}

63
src/cmoball.h Normal file
View file

@ -0,0 +1,63 @@
/*
* Copyright 2021 Jorengarenar <dev@joren.ga>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*/
/**
* from https://github.com/Jorengarenar/CMObALL/
*/
#ifndef CMOBALL_H_
#define CMOBALL_H_
#define CMOBALL(NAME, ...) CMOBALL_OVR(NAME, CMOBALL_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)
#define CMOBALL_OVR(name, num) CMOBALL__OVR(name, num)
#define CMOBALL__OVR(name, num) name ## _ ## num
/* *INDENT-OFF* */
#define CMOBALL_ARG_N( \
_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, \
_10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \
_20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
_30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \
_40, _41, _42, _43, _44, _45, _46, _47, _48, _49, \
_50, _51, _52, _53, _54, _55, _56, _57, _58, _59, \
_60, _61, _62, _63, \
N, ...) N
#define CMOBALL_RSEQ_N \
64, 63, 62, 61, 60, \
59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \
49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \
39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \
29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \
19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
/* *INDENT-ON* */
#define CMOBALL_DETECT_0_ARGS(...) CMOBALL__DETECT_0 ## __VA_ARGS__ ## _
#define CMOBALL__DETECT_0_ ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0
#define CMOBALL__NUM_ARGS(ARGS) CMOBALL_ARG_N ARGS
#define CMOBALL_NUM_ARGS(...) CMOBALL__NUM_ARGS((CMOBALL_DETECT_0_ARGS(__VA_ARGS__), CMOBALL_RSEQ_N))
#endif /* CMOBALL_H_ */

View file

@ -2,9 +2,9 @@
// Created by gwendolyn on 2/2/23.
//
#include "clay-color.h"
#include "color.h"
clay_color clay_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
clay_color color = {.r = r, .g = g, .b = b, .a = a};
clay_color color = {.red = r, .green = g, .blue = b, .alpha = a};
return color;
}

9
src/color.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef CLAY_COLOR_H
#define CLAY_COLOR_H
#include <stdint.h>
#include "clay.h"
clay_color clay_color_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
#endif //CLAY_COLOR_H

157
src/context.c Normal file
View file

@ -0,0 +1,157 @@
#include <clay.h>
#include <stddef.h>
#include <malloc.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#include "layout.h"
#include "context.h"
#include "clay-map.h"
#include "clay-list.h"
// todo: thread safety? maybe as a macro option CLAY_ENABLE_THREADSAFE
CLAY_STRING_MAP_TYPE(property_def_map, clay_prop_definition)
typedef struct {
clay_layout_class class;
property_def_map property_definitions;
} layout_class_container;
CLAY_STRING_MAP_TYPE(layout_class_map, layout_class_container)
struct clay_ctx_t {
layout_class_map layout_classes;
property_def_map global_property_definitions;
clay_layout_list layouts;
};
clay_ctx clay_create_context(void) {
clay_ctx ctx = malloc(sizeof *ctx);
assert(ctx != NULL);
ctx->layouts = clay_list_create(clay_layout_list);
ctx->layout_classes = clay_map_create(layout_class_map);
ctx->global_property_definitions = clay_map_create(property_def_map);
// todo: need to figure out how to do that properly
// every module should register itself somehow, or be manually registered by the user
// the context implementation should not know about the different modules
clay_text_register(ctx);
clay_flex_register(ctx);
clay_document_register(ctx);
return ctx;
}
void clay_destroy_context(clay_ctx ctx) {
CLAY_LIST_FOREACH(ctx->layouts, layout, _) {
clay_layout_cleanup(*layout);
free(*layout);
}
clay_list_destroy(ctx->layouts);
clay_map_destroy(ctx->layout_classes);
clay_map_destroy(ctx->global_property_definitions);
free(ctx);
}
void clay_ctx_register_layout(clay_ctx ctx, clay layout) {
clay_list_append(ctx->layouts, layout);
}
void clay_ctx_unregister_layout(clay_ctx ctx, clay layout) {
CLAY_LIST_FOREACH(ctx->layouts, l, idx) {
if (*l == layout) {
clay_list_remove(ctx->layouts, idx);
break;
}
}
}
static clay_prop_definition transform_property_definition(clay_prop_definition_in *prop_def_in, bool global) {
clay_prop_definition prop_def = {
.allow_int = prop_def_in->allow_int,
.allow_float = prop_def_in->allow_float,
.allow_units = prop_def_in->allow_units,
.allow_color = prop_def_in->allow_color,
.allow_string = prop_def_in->allow_string,
.keywords = clay_map_create(clay_prop_keyword_map),
.inherits = prop_def_in->inherits,
.global = global,
.max_values = (prop_def_in->max_values == 0 ? 1 : prop_def_in->max_values),
};
for (size_t i = 0; i < prop_def_in->keywords.num_entries; ++i) {
clay_map_set(prop_def.keywords, prop_def_in->keywords.entries[i].keyword, prop_def_in->keywords.entries[i].value);
}
return prop_def;
}
void clay_ctx_register_global_property(clay_ctx ctx, const char *name, clay_prop_definition_in *prop_def) {
assert(!clay_map_has_key(ctx->global_property_definitions, name));
clay_map_set(ctx->global_property_definitions, name, transform_property_definition(prop_def, true));
}
void clay_ctx_register_class_property(clay_ctx ctx, const char *class_name, const char *name,
clay_prop_definition_in *prop_def) {
layout_class_container *class_container;
bool class_exists = clay_map_get_ptr(ctx->layout_classes, class_name, &class_container);
assert(class_exists);
if (class_exists) {
assert(!clay_map_has_key(class_container->property_definitions, name));
clay_map_set(class_container->property_definitions, name, transform_property_definition(prop_def, false));
}
}
const clay_prop_definition *clay_ctx_get_property_definition(clay_ctx ctx, clay_layout_class class, const char *name) {
clay_prop_definition *definition;
CLAY_MAP_FOREACH(ctx->layout_classes, _, container) {
(void)_;
if (container->class == class) {
if (clay_map_get_ptr(container->property_definitions, name, &definition)) {
return definition;
}
break;
}
}
if (clay_map_get_ptr(ctx->global_property_definitions, name, &definition)) {
return definition;
}
return NULL;
}
void clay_ctx_register_layout_class(clay_ctx ctx, const char *name, clay_layout_class class) {
assert(!clay_map_has_key(ctx->layout_classes, name));
layout_class_container container = {
.class = class,
.property_definitions = clay_map_create(property_def_map),
};
clay_map_set(ctx->layout_classes, name, container);
}
clay_layout_class clay_ctx_get_layout_class(clay_ctx ctx, const char *name) {
layout_class_container container;
if (clay_map_get(ctx->layout_classes, name, &container)) {
return container.class;
} else {
return NULL;
}
}
const char * clay_ctx_get_layout_class_name(clay_ctx ctx, clay_layout_class class) {
CLAY_MAP_FOREACH(ctx->layout_classes, name, container) {
if (container->class == class){
return name;
}
}
return NULL;
}

30
src/context.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef CLAY_CONTEXT_H
#define CLAY_CONTEXT_H
#include <stdbool.h>
#include "clay-list.h"
#include "property.h"
#include "layout.h"
void clay_ctx_register_layout(clay_ctx ctx, clay layout);
void clay_ctx_unregister_layout(clay_ctx ctx, clay layout);
const clay_prop_definition *clay_ctx_get_property_definition(clay_ctx ctx, clay_layout_class class, const char *name);
void clay_ctx_register_layout_class(clay_ctx ctx, const char *name, clay_layout_class class);
clay_layout_class clay_ctx_get_layout_class(clay_ctx ctx, const char *name);
const char * clay_ctx_get_layout_class_name(clay_ctx ctx, clay_layout_class class);
void clay_ctx_register_global_property(clay_ctx ctx, const char *name, clay_prop_definition_in *prop_def);
void clay_ctx_register_class_property(clay_ctx ctx, const char *class_name, const char *name,
clay_prop_definition_in *prop_def);
#endif //CLAY_CONTEXT_H

105
src/debug.c Normal file
View file

@ -0,0 +1,105 @@
#include <inttypes.h>
#include "clay.h"
#include "layout.h"
static size_t stringify_prop_value(struct clay_prop_val_s val, clay_prop_definition propdef, char *out, size_t max_len);
static void stringify_prop_values(clay_prop_vals_const vals, clay_prop_definition propdef, char *out, size_t max_len);
#define DEBUG_MAX_PROP_STR_LEN 2047
void clay_print_layout_tree(clay doc) {
clay_prop_map props = clay_layout_get_properties(doc);
int depth = 0;
for (clay cur = doc->parent; cur != NULL; cur = cur->parent, depth++);
int indent = depth * 4;
printf("%*s<%s>:\n", indent, "", clay_ctx_get_layout_class_name(doc->ctx, doc->class));
char *prop_string = malloc(DEBUG_MAX_PROP_STR_LEN + 1);
CLAY_MAP_FOREACH(props, prop_name, prop_val_ptr) {
const clay_prop_definition *propdef = clay_ctx_get_property_definition(doc->ctx, doc->class,
prop_name);
stringify_prop_values(prop_val_ptr->values, *propdef, prop_string, DEBUG_MAX_PROP_STR_LEN);
if (prop_val_ptr->inheritance_level == 0) {
printf("%*s %s: %s\n", indent, "", prop_name, prop_string);
} else {
printf("%*s [%s]: %s\n", indent, "", prop_name, prop_string);
}
}
free(prop_string);
clay_layout_list_const children = clay_layout_get_children(doc);
if (children->length > 0) {
printf("%*s children:\n", indent, "");
CLAY_LIST_FOREACH(children, childptr, _) {
clay_print_layout_tree(*childptr);
}
}
}
static void stringify_prop_values(clay_prop_vals_const vals, clay_prop_definition propdef, char *out, size_t max_len) {
size_t pos = 0;
CLAY_LIST_FOREACH(vals, valptr, _) {
pos += stringify_prop_value(*valptr, propdef, out + pos, max_len - pos);
pos += snprintf(out + pos, max_len - pos, " ");
}
}
static size_t
stringify_prop_value(struct clay_prop_val_s val, clay_prop_definition propdef, char *out, size_t max_len) {
size_t len = 0;
switch (val.type) {
case CLAY_PROP_TYPE_KEYWORD: {
bool found = false;
CLAY_MAP_FOREACH(propdef.keywords, keyword_string, numptr) {
if (*numptr == val.keyword) {
len += snprintf(out, max_len - len, "%s", keyword_string);
found = true;
break;
}
}
if (!found) {
len += snprintf(out, max_len - len, "<unknown value>");
}
break;
}
case CLAY_PROP_TYPE_NUMBER: {
if (val.number.type == CLAY_PROP_NUMBER_INT) {
len += snprintf(out, max_len - len, "%" PRId64, val.number.value_int);
} else {
len += snprintf(out, max_len - len, "%f", val.number.value_double);
}
const char *unit_str;
switch (val.number.unit) {
case CLAY_PROP_UNIT_PX:
unit_str = "px";
break;
case CLAY_PROP_UNIT_PT:
unit_str = "pt";
break;
case CLAY_PROP_UNIT_CM:
unit_str = "cm";
break;
case CLAY_PROP_UNIT_MM:
unit_str = "mm";
break;
case CLAY_PROP_UNIT_PERCENT:
unit_str = "%";
break;
default:
unit_str = "";
}
len += snprintf(out + len, max_len - len, "%s", unit_str);
break;
}
case CLAY_PROP_TYPE_COLOR: {
len += snprintf(out, max_len - len, "rgba(%" PRId8 ", %" PRId8 ", %" PRId8 ", %" PRId8 ")",
val.color.red, val.color.green, val.color.blue, val.color.alpha);
break;
}
case CLAY_PROP_TYPE_STRING: {
len += snprintf(out, max_len - len, "'%s'", val.string);
break;
}
}
return len;
}

View file

@ -1,6 +1,7 @@
#include <stddef.h>
#include "clay.h"
#include "clay-color.h"
#include "color.h"
#include "layout.h"
int main(int argc, char **argv) {
@ -11,55 +12,58 @@ int main(int argc, char **argv) {
clay_color c3 = clay_color_rgba(220, 0, 115, 128);
clay_color c4 = clay_color_rgba(0, 139, 248, 128);
clay_color c5 = clay_color_rgba(71, 0, 99, 128);
clay_color cbg = clay_color_rgba(206, 249, 242, 255);
clay t1 = clay_create_text(ctx);
clay_set(t1, "content", "ITEM 1");
clay_set(t1, "bg-color", &c1);
clay t1 = clay_create(ctx, "text");
clay_set(t1, "text", "ITEM 1");
clay_set(t1, "background-color", c1);
clay_set(t1, "width", 100);
clay_set(t1, "flex:grow", 1);
clay_set(t1, "flex:shrink", 1);
clay_set(t1, "flex-grow", 1);
clay_set(t1, "flex-shrink", 1);
clay_set(t1, "align", "center");
clay_set(t1, "vertical-align", "middle");
clay t2 = clay_clone(t1);
clay_set(t2, "content", "ITEM 2");
clay_set(t2, "bg-color", &c2);
clay_set(t2, "text", "ITEM 2");
clay_set(t2, "background-color", c2);
clay_set(t2, "width", 300);
clay t3 = clay_clone(t1);
clay_set(t3, "content", "ITEM 3");
clay_set(t3, "bg-color", &c3);
clay_set(t3, "text", "ITEM 3");
clay_set(t3, "background-color", c3);
clay_set(t3, "width", 200);
clay t4 = clay_clone(t1);
clay_set(t4, "content", "ITEM 4");
clay_set(t4, "bg-color", &c4);
clay_set(t4, "text", "ITEM 4");
clay_set(t4, "background-color", c4);
clay_set(t4, "width", 400);
clay t5 = clay_clone(t1);
clay_set(t5, "content", "ITEM 5");
clay_set(t5, "bg-color", &c5);
clay_set(t5, "text", "ITEM 5");
clay_set(t5, "background-color", c5);
clay_set(t5, "width", 250);
clay flex = clay_create_flex(ctx);
clay flex = clay_create(ctx, "flex");
clay_set(flex, "direction", "row");
clay_set(flex, "wrap", "wrap");
clay_set(flex, "align-items", "stretch");
clay_set(flex, "align-content", "stretch");
clay_set(flex, "gap", 20);
clay_set(flex, "padding", 20);
clay_set(flex, "contents", (clay[]) {t1, t2, t3, t4, t5, NULL});
clay_append_child(flex, t1);
clay_append_child(flex, t2);
clay_append_child(flex, t3);
clay_append_child(flex, t4);
clay_append_child(flex, t5);
clay doc = clay_create_document(ctx);
clay doc = clay_create(ctx, "document");
clay_set(doc, "width", 800);
clay_set(doc, "height", 400);
clay_set(doc, "bg-color", &cbg);
clay_set(doc, "content", flex);
clay_set(doc, "background-color", "#cef9f2ff");
clay_append_child(doc, flex);
clay_debug_layout(doc);
clay_print_layout_tree(doc);
clay_destroy_context(ctx);
}

77
src/document.c Normal file
View file

@ -0,0 +1,77 @@
#include "clay.h"
#include "layout.h"
#include "context.h"
// TODO: what is the document layout class even needed for? couldn't we just have any layout as the root?
static struct clay_layout_class_s layout_class_document = {};
static clay_prop_definition_in prop_numeric_single = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_ANY_UNIT,
};
static clay_prop_definition_in prop_numeric_two = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_ANY_UNIT,
.max_values = 2,
};
static clay_prop_definition_in prop_numeric_four = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_ANY_UNIT,
.max_values = 4,
};
static clay_prop_definition_in prop_numeric_eight = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_ANY_UNIT,
.max_values = 8,
};
static clay_prop_definition_in prop_color_single = {
.allow_color = true,
};
static clay_prop_definition_in prop_color_four = {
.allow_color = true,
.max_values = 4,
};
void clay_document_register(clay_ctx ctx) {
clay_ctx_register_layout_class(ctx, "document", &layout_class_document);
clay_ctx_register_global_property(ctx, "width", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "height", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "padding", &prop_numeric_four);
clay_ctx_register_global_property(ctx, "padding-left", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "padding-right", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "padding-top", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "padding-bottom", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "margin", &prop_numeric_four);
clay_ctx_register_global_property(ctx, "margin-left", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "margin-right", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "margin-top", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "margin-bottom", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "background-color", &prop_color_single);
clay_ctx_register_global_property(ctx, "border-width", &prop_numeric_four);
clay_ctx_register_global_property(ctx, "border-width-left", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "border-width-right", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "border-width-top", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "border-width-bottom", &prop_numeric_single);
clay_ctx_register_global_property(ctx, "border-color", &prop_color_four);
clay_ctx_register_global_property(ctx, "border-color-left", &prop_color_single);
clay_ctx_register_global_property(ctx, "border-color-right", &prop_color_single);
clay_ctx_register_global_property(ctx, "border-color-top", &prop_color_single);
clay_ctx_register_global_property(ctx, "border-color-bottom", &prop_color_single);
clay_ctx_register_global_property(ctx, "border-radius", &prop_numeric_eight);
clay_ctx_register_global_property(ctx, "border-radius-top-left", &prop_numeric_two);
clay_ctx_register_global_property(ctx, "border-radius-top-right", &prop_numeric_two);
clay_ctx_register_global_property(ctx, "border-radius-bottom-right", &prop_numeric_two);
clay_ctx_register_global_property(ctx, "border-radius-bottom-left", &prop_numeric_two);
}

144
src/flex.c Normal file
View file

@ -0,0 +1,144 @@
#include "clay.h"
#include "layout.h"
#include "context.h"
static struct clay_layout_class_s layout_class_flex = {};
enum flex_direction {
FLEX_DIRECTION_ROW,
FLEX_DIRECTION_ROW_REVERSE,
FLEX_DIRECTION_COLUMN,
FLEX_DIRECTION_COLUMN_REVERSE,
};
static clay_prop_definition_in prop_direction = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("row", FLEX_DIRECTION_ROW),
CLAY_PROP_DEF_KEYWORD("row-reverse", FLEX_DIRECTION_ROW_REVERSE),
CLAY_PROP_DEF_KEYWORD("column", FLEX_DIRECTION_COLUMN),
CLAY_PROP_DEF_KEYWORD("column-reverse", FLEX_DIRECTION_COLUMN_REVERSE),
),
};
enum flex_wrap {
FLEX_WRAP_NOWRAP,
FLEX_WRAP_WRAP,
FLEX_WRAP_WRAP_REVERSE,
};
static clay_prop_definition_in prop_wrap = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("nowrap", FLEX_WRAP_NOWRAP),
CLAY_PROP_DEF_KEYWORD("wrap", FLEX_WRAP_WRAP),
CLAY_PROP_DEF_KEYWORD("wrap-reverse", FLEX_WRAP_WRAP_REVERSE),
),
};
enum justify_content {
JUSTIFY_CONTENT_START,
JUSTIFY_CONTENT_END,
JUSTIFY_CONTENT_CENTER,
JUSTIFY_CONTENT_SPACE_BETWEEN,
JUSTIFY_CONTENT_SPACE_AROUND,
JUSTIFY_CONTENT_SPACE_EVENLY,
};
static clay_prop_definition_in prop_justify_content = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("start", JUSTIFY_CONTENT_START),
CLAY_PROP_DEF_KEYWORD("end", JUSTIFY_CONTENT_END),
CLAY_PROP_DEF_KEYWORD("center", JUSTIFY_CONTENT_CENTER),
CLAY_PROP_DEF_KEYWORD("space-between", JUSTIFY_CONTENT_SPACE_BETWEEN),
CLAY_PROP_DEF_KEYWORD("space-around", JUSTIFY_CONTENT_SPACE_AROUND),
CLAY_PROP_DEF_KEYWORD("space-evenly", JUSTIFY_CONTENT_SPACE_EVENLY),
),
};
enum align_items {
ALIGN_ITEMS_START,
ALIGN_ITEMS_END,
ALIGN_ITEMS_CENTER,
ALIGN_ITEMS_STRETCH,
};
static clay_prop_definition_in prop_align_items = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("start", ALIGN_ITEMS_START),
CLAY_PROP_DEF_KEYWORD("end", ALIGN_ITEMS_END),
CLAY_PROP_DEF_KEYWORD("center", ALIGN_ITEMS_CENTER),
CLAY_PROP_DEF_KEYWORD("stretch", ALIGN_ITEMS_STRETCH),
),
};
enum align_content {
ALIGN_CONTENT_START,
ALIGN_CONTENT_END,
ALIGN_CONTENT_CENTER,
ALIGN_CONTENT_STRETCH,
ALIGN_CONTENT_SPACE_BETWEEN,
ALIGN_CONTENT_SPACE_AROUND,
ALIGN_CONTENT_SPACE_EVENLY,
};
static clay_prop_definition_in prop_align_content = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("start", ALIGN_CONTENT_START),
CLAY_PROP_DEF_KEYWORD("end", ALIGN_CONTENT_END),
CLAY_PROP_DEF_KEYWORD("center", ALIGN_CONTENT_CENTER),
CLAY_PROP_DEF_KEYWORD("stretch", ALIGN_CONTENT_STRETCH),
CLAY_PROP_DEF_KEYWORD("space-between", ALIGN_CONTENT_SPACE_BETWEEN),
CLAY_PROP_DEF_KEYWORD("space-around", ALIGN_CONTENT_SPACE_AROUND),
CLAY_PROP_DEF_KEYWORD("space-evenly", ALIGN_CONTENT_SPACE_EVENLY),
),
};
static clay_prop_definition_in prop_gap = {
.allow_float = true,
.allow_int = true,
.allow_units = CLAY_PROP_UNIT_ABSOLUTE | CLAY_PROP_UNIT_PX,
.max_values = 2,
};
static clay_prop_definition_in prop_gap_single = {
.allow_float = true,
.allow_int = true,
.allow_units = CLAY_PROP_UNIT_ABSOLUTE | CLAY_PROP_UNIT_PX,
};
static clay_prop_definition_in prop_flex_order = {
.allow_int = true,
.allow_units = CLAY_PROP_UNIT_NONE,
};
static clay_prop_definition_in prop_flex_grow_shrink = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_NONE,
};
static clay_prop_definition_in prop_flex_basis = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_ANY_UNIT,
};
void clay_flex_register(clay_ctx ctx) {
clay_ctx_register_layout_class(ctx, "flex", &layout_class_flex);
clay_ctx_register_class_property(ctx, "flex", "direction", &prop_direction);
clay_ctx_register_class_property(ctx, "flex", "wrap", &prop_wrap);
clay_ctx_register_class_property(ctx, "flex", "justify-content", &prop_justify_content);
clay_ctx_register_class_property(ctx, "flex", "align-items", &prop_align_items);
clay_ctx_register_class_property(ctx, "flex", "align-content", &prop_align_content);
clay_ctx_register_class_property(ctx, "flex", "gap", &prop_gap);
clay_ctx_register_class_property(ctx, "flex", "column-gap", &prop_gap_single);
clay_ctx_register_class_property(ctx, "flex", "row-gap", &prop_gap_single);
clay_ctx_register_global_property(ctx, "order", &prop_flex_order);
clay_ctx_register_global_property(ctx, "flex-grow", &prop_flex_grow_shrink);
clay_ctx_register_global_property(ctx, "flex-shrink", &prop_flex_grow_shrink);
clay_ctx_register_global_property(ctx, "flex-basis", &prop_flex_basis);
clay_ctx_register_global_property(ctx, "align-self", &prop_align_items);
}

84
src/layout.c Normal file
View file

@ -0,0 +1,84 @@
#include <malloc.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>
#include "layout.h"
#include "context.h"
#include "clay.h"
clay clay_create(clay_ctx ctx, const char *layout_class_name) {
clay_layout_class class = clay_ctx_get_layout_class(ctx, layout_class_name);
return clay_layout_create(ctx, class);
}
clay clay_layout_create(clay_ctx ctx, clay_layout_class class) {
clay layout = malloc(sizeof(*layout));
assert(layout != NULL);
layout->properties = clay_map_create(clay_property_map, 5);
layout->children = clay_list_create(clay_layout_list);
layout->class = class;
layout->ctx = ctx;
if (layout->class->init != NULL) {
layout->class->init(layout);
}
clay_ctx_register_layout(ctx, layout);
return layout;
}
void clay_layout_destroy(clay layout) {
clay_ctx_unregister_layout(layout->ctx, layout);
clay_layout_cleanup(layout);
free(layout);
}
void clay_layout_cleanup(clay layout) {
if (layout->class->cleanup != NULL) {
layout->class->cleanup(layout);
}
clay_map_destroy(layout->properties);
clay_list_destroy(layout->children);
}
clay clay_clone(clay layout) {
clay cloned = malloc(sizeof(*cloned));
cloned->class = layout->class;
cloned->ctx = layout->ctx;
cloned->properties = clay_map_clone(layout->properties);
cloned->children = clay_list_create(clay_layout_list);
clay_ctx_register_layout(layout->ctx, cloned);
return cloned;
}
clay clay_clone_recursive(clay layout) {
// todo: should this function be made non-recursive (i.e. replace with loop + manual stack)?
clay cloned = clay_clone(layout);
CLAY_LIST_FOREACH(layout->children, child, _) {
clay cloned_child = clay_clone_recursive(*child);
clay_list_append(cloned->children, cloned_child);
}
return cloned;
}
void clay_append_child(clay parent, clay child) {
if (child->parent != NULL) {
CLAY_LIST_FOREACH(child->parent->children, el_ptr, idx) {
if (*el_ptr == child) {
clay_list_remove(child->parent->children, idx);
break;
}
}
}
clay_list_append(parent->children, child);
child->parent = parent;
}
clay_layout_list_const clay_layout_get_children(clay layout) {
return layout->children;
}
clay clay_layout_get_parent(clay layout) {
return layout->parent;
}

34
src/layout.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef CLAY_LAYOUT_H
#define CLAY_LAYOUT_H
#include <stdbool.h>
#include "clay.h"
#include "clay-map.h"
#include "clay-list.h"
typedef struct clay_layout_class_s *clay_layout_class;
CLAY_LIST_TYPE(clay_layout_list, clay)
#include "color.h"
#include "context.h"
typedef void (*clay_layout_init_fn)(clay);
typedef void (*clay_layout_cleanup_fn)(clay);
struct clay_layout_class_s {
clay_layout_init_fn init;
clay_layout_cleanup_fn cleanup;
};
clay clay_layout_create(clay_ctx ctx, clay_layout_class class);
void clay_layout_cleanup(clay layout);
clay_layout_list_const clay_layout_get_children(clay layout);
clay clay_layout_get_parent(clay layout);
#endif //CLAY_LAYOUT_H

281
src/property.c Normal file
View file

@ -0,0 +1,281 @@
#include "property.h"
#include <assert.h>
#include <ctype.h>
#include <locale.h>
#include "clay.h"
#include "context.h"
#include "clay-map.h"
static struct clay_prop_val_s parse_prop_val_int(int val, const clay_prop_definition *def);
static struct clay_prop_val_s parse_prop_val_double(double val, const clay_prop_definition *def);
static struct clay_prop_val_s parse_prop_val_string_numeric(const char *val, const clay_prop_definition *def);
static struct clay_prop_val_s parse_prop_val_string_notnumeric(const char *val, const clay_prop_definition *def);
static struct clay_prop_val_s parse_prop_val_string(const char *val, const clay_prop_definition *def);
static struct clay_prop_val_s parse_prop_val_color(clay_color val, const clay_prop_definition *def);
static struct clay_prop_val_s parse_prop_val(struct clay_prop_val_in in, const clay_prop_definition *def);
clay_prop clay_layout_get_property(clay layout, const char *prop_name) {
const clay_prop_definition *prop_def = clay_ctx_get_property_definition(layout->ctx, layout->class, prop_name);
clay_prop prop;
if (!prop_def->inherits) {
if (!clay_map_get(layout->properties, prop_name, &(prop.values))) {
prop.values = NULL;
}
return prop;
}
bool found = false;
clay cur = layout;
prop.inheritance_level = 0;
while (cur != NULL) {
if (prop_def->global || layout->class == cur->class) {
if (clay_map_get(cur->properties, prop_name, &prop.values)) {
found = true;
break;
}
}
cur = cur->parent;
prop.inheritance_level += 1;
}
if (!found) {
prop.values = NULL;
prop.inheritance_level = 0;
}
return prop;
}
clay_prop_map clay_layout_get_properties(clay layout) {
clay_prop_map props = clay_map_create(clay_prop_map);
clay cur = layout;
int inheritance_level = 0;
while (cur != NULL) {
CLAY_MAP_FOREACH(cur->properties, propname, propvalueptr) {
const clay_prop_definition *propdef = clay_ctx_get_property_definition(cur->ctx, cur->class, propname);
if (inheritance_level == 0 || (propdef->inherits && (propdef->global || cur->class == layout->class))) {
if (!clay_map_has_key(props, propname)) {
clay_prop prop = {.values = *propvalueptr, .inheritance_level = inheritance_level};
clay_map_set(props, propname, prop);
}
}
}
cur = cur->parent;
inheritance_level += 1;
}
return props;
}
void clay_layout_set_property(clay layout, const char *prop_name, int num_values, ...) {
clay_ctx ctx = layout->ctx;
const clay_prop_definition *def = clay_ctx_get_property_definition(ctx, layout->class, prop_name);
assert(def != NULL);
assert(num_values <= def->max_values);
clay_prop_vals vals = clay_list_create(clay_prop_vals, num_values);
if (num_values > 0) {
va_list args;
va_start(args, num_values);
for (int i = 0; i < num_values; ++i) {
struct clay_prop_val_in inval = va_arg(args, struct clay_prop_val_in);
struct clay_prop_val_s val = parse_prop_val(inval, def);
clay_list_append(vals, val);
}
va_end(args);
}
clay_map_set(layout->properties, prop_name, vals);
}
static struct clay_prop_val_s parse_prop_val_int(int val, const clay_prop_definition *def) {
assert(def->allow_int);
clay_prop_val_unit unit;
if (!(def->allow_units & CLAY_PROP_UNIT_NONE) && def->allow_units & CLAY_PROP_UNIT_PX) {
unit = CLAY_PROP_UNIT_PX;
} else {
unit = CLAY_PROP_UNIT_NONE;
}
assert(def->allow_units & unit);
return (struct clay_prop_val_s) {
.type = CLAY_PROP_TYPE_NUMBER,
.number = (struct clay_prop_val_number) {
.type = CLAY_PROP_NUMBER_INT,
.unit = unit,
.value_int = val
}
};
}
static struct clay_prop_val_s parse_prop_val_double(double val, const clay_prop_definition *def) {
assert(def->allow_float);
assert(def->allow_units & CLAY_PROP_UNIT_NONE);
return (struct clay_prop_val_s) {
.type = CLAY_PROP_TYPE_NUMBER,
.number = (struct clay_prop_val_number) {
.type = CLAY_PROP_NUMBER_FLOAT,
.unit = CLAY_PROP_UNIT_NONE,
.value_double = val
}
};
}
#include <stdlib.h>
static clay_prop_val parse_prop_val_string_numeric(const char *val, const clay_prop_definition *def) {
// TODO: it is wasteful to call newlocale for every parse, cache globally
// or find a locale-independent correct implementation of the strto* functions
clay_prop_val result = {
.type = CLAY_PROP_TYPE_NUMBER,
.number = {}
};
locale_t locale = newlocale(LC_ALL, "C", NULL);
locale_t original = uselocale(locale);
char * endptr;
int64_t int_val = strtoll(val, &endptr, 10);
ptrdiff_t int_len = endptr - val;
double float_val = strtod(val, &endptr);
ptrdiff_t float_len = endptr - val;
const char * nextptr;
if (float_len > int_len || (float_len == int_len && !def->allow_int)) {
assert(def->allow_float);
result.number.type = CLAY_PROP_NUMBER_FLOAT;
result.number.value_double = float_val;
nextptr = val + float_len;
} else {
assert(def->allow_int);
result.number.type = CLAY_PROP_NUMBER_INT;
result.number.value_int = int_val;
nextptr = val + int_len;
}
// consume spaces after number
while(isspace(*nextptr)) ++nextptr;
if (strncmp(nextptr, "px", 2) == 0) {
result.number.unit = CLAY_PROP_UNIT_PX;
assert(def->allow_units & CLAY_PROP_UNIT_PX);
nextptr += 2;
} else if (strncmp(nextptr, "pt", 2) == 0) {
result.number.unit = CLAY_PROP_UNIT_PT;
assert(def->allow_units & CLAY_PROP_UNIT_PT);
nextptr += 2;
} else if(strncmp(nextptr, "cm", 2) == 0) {
result.number.unit = CLAY_PROP_UNIT_CM;
assert(def->allow_units & CLAY_PROP_UNIT_CM);
nextptr += 2;
} else if(strncmp(nextptr, "mm", 2) == 0) {
result.number.unit = CLAY_PROP_UNIT_MM;
assert(def->allow_units & CLAY_PROP_UNIT_MM);
nextptr += 2;
} else if(strncmp(nextptr, "%", 1) == 0) {
result.number.unit = CLAY_PROP_UNIT_PERCENT;
assert(def->allow_units & CLAY_PROP_UNIT_PERCENT);
nextptr += 1;
} else {
if (def->allow_units & CLAY_PROP_UNIT_NONE) {
result.number.unit = CLAY_PROP_UNIT_NONE;
} else if (def->allow_units & CLAY_PROP_UNIT_PX) {
result.number.unit = CLAY_PROP_UNIT_PX;
} else {
assert(false);
}
}
// consume spaces after unit
while(isspace(*nextptr)) ++nextptr;
// ensure nothing except whitespace appeared after the number(+unit)
assert(*nextptr == '\0');
uselocale(original);
return result;
}
static struct clay_prop_val_s parse_prop_val_string_notnumeric(const char *val, const clay_prop_definition *def) {
if (val[0] == '#') {
size_t len = strlen(val) - 1;
if (len == 3 || len == 4 || len == 6 || len == 8) {
bool is_color = true;
uint8_t parts[8] = {0};
for (size_t i = 0; i < len; ++i) {
uint8_t ch = val[1 + i];
if (ch >= '0' && ch <= '9') {
parts[i] = ch - '0';
} else if (ch >= 'a' && ch <= 'f') {
parts[i] = ch - 'a' + 0xA;
} else if (ch >= 'A' && ch <= 'F') {
parts[i] = ch - 'A' + 0xA;
} else {
is_color = false;
break;
}
}
if (is_color) {
clay_color color = {};
switch (len) {
case 3:
color.red = parts[0] << 4 | parts[0];
color.green = parts[1] << 4 | parts[1];
color.blue = parts[2] << 4 | parts[2];
color.alpha = 255;
break;
case 4:
color.red = parts[0] << 4 | parts[0];
color.green = parts[1] << 4 | parts[1];
color.blue = parts[2] << 4 | parts[2];
color.alpha = parts[3] << 4 | parts[3];
break;
case 6:
color.red = parts[0] << 4 | parts[1];
color.green = parts[2] << 4 | parts[3];
color.blue = parts[4] << 4 | parts[5];
color.alpha = 255;
break;
case 8:
color.red = parts[0] << 4 | parts[1];
color.green = parts[2] << 4 | parts[3];
color.blue = parts[4] << 4 | parts[5];
color.alpha = parts[6] << 4 | parts[7];
break;
default:
assert(false);
}
return (struct clay_prop_val_s) {.type=CLAY_PROP_TYPE_COLOR, .color=color};
}
}
}
int keyword_val;
if (clay_map_get(def->keywords, val, &keyword_val)) {
return (struct clay_prop_val_s) {.type=CLAY_PROP_TYPE_KEYWORD, .keyword=keyword_val};
}
assert(def->allow_string);
return (struct clay_prop_val_s) {.type=CLAY_PROP_TYPE_STRING, .string=val};
}
static struct clay_prop_val_s parse_prop_val_string(const char *val, const clay_prop_definition *def) {
assert(val != NULL && val[0] != '\0'); // no null refs and no empty strings
if (isdigit(val[0]) || (val[0] == '-' && isdigit(val[0]))) {
return parse_prop_val_string_numeric(val, def);
} else {
return parse_prop_val_string_notnumeric(val, def);
}
}
static struct clay_prop_val_s parse_prop_val_color(clay_color val, const clay_prop_definition *def) {
assert(def->allow_color);
return (struct clay_prop_val_s) {.type = CLAY_PROP_TYPE_COLOR, .color = val};
}
static struct clay_prop_val_s parse_prop_val(struct clay_prop_val_in in, const clay_prop_definition *def) {
switch (in.type) {
case TYPE_INT:
return parse_prop_val_int(in.value_int, def);
case TYPE_DOUBLE:
return parse_prop_val_double(in.value_double, def);
case TYPE_STRING:
return parse_prop_val_string(in.value_string, def);
case TYPE_COLOR:
return parse_prop_val_color(in.value_color, def);
}
assert(false);
}

161
src/property.h Normal file
View file

@ -0,0 +1,161 @@
#ifndef CLAY_PROPERTY_H
#define CLAY_PROPERTY_H
#include "clay.h"
#include "clay-list.h"
#include "clay-map.h"
CLAY_STRING_MAP_TYPE(clay_prop_keyword_map, int)
typedef struct clay_prop_val_s clay_prop_val;
CLAY_LIST_TYPE(clay_prop_vals, clay_prop_val)
typedef struct clay_prop_s clay_prop;
typedef struct clay_prop_definition_s clay_prop_definition;
typedef struct clay_prop_definition_in_s clay_prop_definition_in;
#include "layout.h"
struct clay_prop_s {
clay_prop_vals_const values;
int inheritance_level;
} ;
CLAY_STRING_MAP_TYPE(clay_property_map, clay_prop_vals)
CLAY_STRING_MAP_TYPE(clay_prop_map, clay_prop)
struct clay_t {
clay_ctx ctx;
clay_layout_class class;
clay_property_map properties;
clay parent;
clay_layout_list children;
};
enum clay_prop_val_type {
CLAY_PROP_TYPE_KEYWORD,
CLAY_PROP_TYPE_NUMBER,
CLAY_PROP_TYPE_COLOR,
CLAY_PROP_TYPE_STRING
};
enum clay_prop_val_number_type {
CLAY_PROP_NUMBER_INT,
CLAY_PROP_NUMBER_FLOAT,
};
typedef enum {
CLAY_PROP_UNIT_NONE = 1,
CLAY_PROP_UNIT_PX = 2,
CLAY_PROP_UNIT_PT = 4,
CLAY_PROP_UNIT_CM = 8,
CLAY_PROP_UNIT_MM = 16,
CLAY_PROP_UNIT_PERCENT = 32,
} clay_prop_val_unit;
#define CLAY_PROP_UNIT_ABSOLUTE (CLAY_PROP_UNIT_PT | CLAY_PROP_UNIT_CM | CLAY_PROP_UNIT_MM)
#define CLAY_PROP_UNIT_ANY_UNIT (CLAY_PROP_UNIT_PX | CLAY_PROP_UNIT_PT | CLAY_PROP_UNIT_CM | CLAY_PROP_UNIT_MM | CLAY_PROP_UNIT_PERCENT)
#define CLAY_PROP_UNIT_ANY (CLAY_PROP_UNIT_NONE | CLAY_PROP_UNIT_ANY_UNIT)
struct clay_prop_val_number {
enum clay_prop_val_number_type type;
clay_prop_val_unit unit;
union {
int64_t value_int;
double value_double;
};
};
struct clay_prop_val_s {
enum clay_prop_val_type type;
union {
int keyword;
struct clay_prop_val_number number;
clay_color color;
const char *string;
};
};
struct clay_prop_definition_s {
bool allow_int;
bool allow_float;
bool allow_string;
clay_prop_val_unit allow_units;
bool allow_color;
clay_prop_keyword_map keywords;
int max_values;
bool inherits;
bool global;
};
struct clay_prop_keyword_map_entry_in {
const char *keyword;
int value;
};
struct clay_prop_keyword_map_in {
struct clay_prop_keyword_map_entry_in *entries;
size_t num_entries;
};
struct clay_prop_definition_in_s {
bool allow_int;
bool allow_string;
bool allow_float;
clay_prop_val_unit allow_units;
bool allow_color;
struct clay_prop_keyword_map_in keywords;
int max_values;
bool inherits;
} ;
#define CLAY_PP_NARG(...) \
CLAY_PP_NARG_(__VA_ARGS__,CLAY_PP_RSEQ_N())
#define CLAY_PP_NARG_(...) \
CLAY_PP_ARG_N(__VA_ARGS__)
#define CLAY_PP_ARG_N(\
_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, \
_11, _12, _13, _14, _15, _16, _17, _18, _19, _20, \
_21, _22, _23, _24, _25, _26, _27, _28, _29, _30, \
_31, _32, _33, _34, _35, _36, _37, _38, _39, _40, \
_41, _42, _43, _44, _45, _46, _47, _48, _49, _50, \
_51, _52, _53, _54, _55, _56, _57, _58, _59, _60, \
_61, _62, _63, N, ...) N
#define CLAY_PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
#define CLAY_PROP_DEF_KEYWORD(k, v) {.keyword = k, .value = v}
#define CLAY_PROP_DEF_KEYWORDS(...) (struct clay_prop_keyword_map_in) { \
.entries = (struct clay_prop_keyword_map_entry_in[]) { \
__VA_ARGS__ \
}, \
.num_entries = CLAY_PP_NARG(__VA_ARGS__)/2 \
}
clay_prop clay_layout_get_property(clay layout, const char *prop_name);
/*
* this function allocates a clay_property_map, make sure to free it with clay_map_destroy
* the values in the returned map are pointers to the actual property values and should not be modified
*/
clay_prop_map clay_layout_get_properties(clay layout);
#endif //CLAY_PROPERTY_H

8
src/render.c Normal file
View file

@ -0,0 +1,8 @@
#include "clay.h"
void clay_render_to_png(clay doc, const char *path) {
// todo
}

130
src/text.c Normal file
View file

@ -0,0 +1,130 @@
#include "clay.h"
#include "layout.h"
static struct clay_layout_class_s layout_class_text = {};
static clay_prop_definition_in prop_align = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("left", 0),
CLAY_PROP_DEF_KEYWORD("center", 1),
CLAY_PROP_DEF_KEYWORD("right", 2),
CLAY_PROP_DEF_KEYWORD("justify", 3),
),
};
static clay_prop_definition_in prop_vertical_align = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("top", 0),
CLAY_PROP_DEF_KEYWORD("middle", 1),
CLAY_PROP_DEF_KEYWORD("bottom", 2),
),
};
static clay_prop_definition_in prop_font_size = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_PT | CLAY_PROP_UNIT_NONE,
.inherits = true,
};
static clay_prop_definition_in prop_line_height = {
.allow_int = true,
.allow_float = true,
.allow_units = CLAY_PROP_UNIT_PT | CLAY_PROP_UNIT_NONE,
.keywords = CLAY_PROP_DEF_KEYWORDS(CLAY_PROP_DEF_KEYWORD("auto", 1)),
.inherits = true,
};
static clay_prop_definition_in prop_font_weight = {
.allow_int = true,
.allow_units = CLAY_PROP_UNIT_NONE,
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("thin", 100),
CLAY_PROP_DEF_KEYWORD("extra-light", 200),
CLAY_PROP_DEF_KEYWORD("ultra-light", 200),
CLAY_PROP_DEF_KEYWORD("light", 300),
CLAY_PROP_DEF_KEYWORD("normal", 400),
CLAY_PROP_DEF_KEYWORD("regular", 400),
CLAY_PROP_DEF_KEYWORD("medium", 500),
CLAY_PROP_DEF_KEYWORD("semi-bold", 600),
CLAY_PROP_DEF_KEYWORD("demi-bold", 600),
CLAY_PROP_DEF_KEYWORD("bold", 700),
CLAY_PROP_DEF_KEYWORD("extra-bold", 800),
CLAY_PROP_DEF_KEYWORD("black", 900),
),
.inherits = true,
};
enum text_font_style {
FONT_STYLE_NORMAL,
FONT_STYLE_ITALIC,
};
static clay_prop_definition_in prop_font_style = {
.allow_int = true,
.allow_units = CLAY_PROP_UNIT_NONE,
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("normal", FONT_STYLE_NORMAL),
CLAY_PROP_DEF_KEYWORD("italic", FONT_STYLE_ITALIC),
),
.inherits = true,
};
static clay_prop_definition_in prop_language = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("revert", 1),
),
.allow_string = true,
.inherits = true,
};
enum text_font_family {
FONT_FAMILY_SERIF,
FONT_FAMILY_SANS_SERIF,
FONT_FAMILY_MONOSPACE,
FONT_FAMILY_EMOJI,
FONT_FAMILY_REVERT,
};
static clay_prop_definition_in prop_font_family = {
.keywords = CLAY_PROP_DEF_KEYWORDS(
CLAY_PROP_DEF_KEYWORD("serif", FONT_FAMILY_SERIF),
CLAY_PROP_DEF_KEYWORD("sans-serif", FONT_FAMILY_SANS_SERIF),
CLAY_PROP_DEF_KEYWORD("monospace", FONT_FAMILY_MONOSPACE),
CLAY_PROP_DEF_KEYWORD("emoji", FONT_FAMILY_EMOJI),
CLAY_PROP_DEF_KEYWORD("revert", FONT_FAMILY_REVERT),
),
.allow_string = true,
.inherits = true,
};
static clay_prop_definition_in prop_text_color = {
.allow_color = true,
.inherits = true,
};
static clay_prop_definition_in prop_text_text = {
.allow_string = true,
};
void clay_text_register(clay_ctx ctx) {
clay_ctx_register_layout_class(ctx, "text", &layout_class_text);
clay_ctx_register_class_property(ctx, "text", "align", &prop_align);
clay_ctx_register_class_property(ctx, "text", "vertical-align", &prop_vertical_align);
clay_ctx_register_class_property(ctx, "text", "text", &prop_text_text);
clay_ctx_register_global_property(ctx, "font-size", &prop_font_size);
clay_ctx_register_global_property(ctx, "font-weight", &prop_font_weight);
clay_ctx_register_global_property(ctx, "font-family", &prop_font_family);
clay_ctx_register_global_property(ctx, "font-style", &prop_font_style);
clay_ctx_register_global_property(ctx, "line-height", &prop_line_height);
clay_ctx_register_global_property(ctx, "language", &prop_language);
clay_ctx_register_global_property(ctx, "text-color", &prop_text_color);
}