clay/src/property.c
Gwendolyn d89ef83551 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.
2023-02-08 01:09:21 +01:00

281 lines
11 KiB
C

#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);
}