d89ef83551
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.
281 lines
11 KiB
C
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);
|
|
} |