RetroArch/gfx/video_layout/load.c
twinaphex ebaa5b9941 Undefine HAVE_VIDEO_LAYOUT for now instead of implicitly enabling
it for every platform - will have to be individually enabled for
each platform first after rigorous testing
2019-05-13 01:57:26 +02:00

765 lines
19 KiB
C

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <formats/rxml.h>
#include "../../verbosity.h"
#include "internal.h"
#include "view.h"
#include "scope.h"
int video_layout_io_find(const char *name);
static const char *const comp_type_str[] = {
NULL, /* VIDEO_LAYOUT_C_UNKNOWN */
NULL, /* VIDEO_LAYOUT_C_SCREEN */
"rect",
"disk",
"image",
"text",
"dotmatrixdot",
"dotmatrix5dot",
"dotmatrix",
"led7seg",
"led8seg_gts1",
"led14seg",
"led14segsc",
"led16seg",
"led16segsc",
"simplecounter",
"reel"
};
static const char *const video_layout_internal_device_params[] =
{
"devicetag" , ":",
"devicebasetag" , "root",
"devicename" , "RetroArch",
"deviceshortname" , "libretro"
};
static const char *const video_layout_internal_screen_params[] =
{
"scr#physicalxaspect" , "1",
"scr#physicalyaspect" , "1",
"scr#nativexaspect" , "1",
"scr#nativeyaspect" , "1",
"scr#width" , "1",
"scr#height" , "1"
};
static int child_count(rxml_node_t *node)
{
int res;
rxml_node_t *child;
res = 0;
for (child = node->children; child; child = child->next)
++res;
return res;
}
static comp_type_t comp_type_from_str(const char *s)
{
size_t i;
for (i = 2; i < ARRAY_SIZE(comp_type_str); ++i)
{
if (strcmp(s, comp_type_str[i]) == 0)
return (comp_type_t)(int)i;
}
return VIDEO_LAYOUT_C_UNKNOWN;
}
static void init_device_params(scope_t *scope)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(video_layout_internal_device_params); i += 2)
{
scope_param(scope, video_layout_internal_device_params[i], video_layout_internal_device_params[i + 1]);
}
}
static void init_screen_params(scope_t *scope, int screen_index)
{
char buf[64];
size_t i;
for (i = 0; i < ARRAY_SIZE(video_layout_internal_screen_params); i += 2)
{
strcpy(buf, video_layout_internal_screen_params[i + 1]);
buf[3] = '0' + screen_index;
scope_param(scope, video_layout_internal_screen_params[i], buf);
}
}
static video_layout_bounds_t parse_bounds(scope_t *scope, rxml_node_t *node)
{
video_layout_bounds_t bounds;
const char *prop;
bounds = make_bounds_unit();
if ((prop = scope_eval(scope, rxml_node_attrib(node, "x")))) bounds.x = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "y")))) bounds.y = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "width")))) bounds.w = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "height")))) bounds.h = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "left")))) bounds.x = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "top")))) bounds.y = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "right")))) bounds.w = get_dec(prop) - bounds.x;
if ((prop = scope_eval(scope, rxml_node_attrib(node, "bottom")))) bounds.h = get_dec(prop) - bounds.y;
return bounds;
}
static video_layout_color_t parse_color(scope_t *scope, rxml_node_t *node)
{
video_layout_color_t color;
const char *prop;
color = make_color_white();
if ((prop = scope_eval(scope, rxml_node_attrib(node, "red")))) color.r = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "green")))) color.g = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "blue")))) color.b = get_dec(prop);
if ((prop = scope_eval(scope, rxml_node_attrib(node, "alpha")))) color.a = get_dec(prop);
return color;
}
static video_layout_orientation_t parse_orientation(scope_t *scope, rxml_node_t *node)
{
video_layout_orientation_t result;
const char *prop;
result = VIDEO_LAYOUT_ROT0;
if ((prop = scope_eval(scope, rxml_node_attrib(node, "rotate"))))
{
if (strcmp(prop, "90") == 0)
result = VIDEO_LAYOUT_ROT90;
else if (strcmp(prop, "180") == 0)
result = VIDEO_LAYOUT_ROT180;
else if (strcmp(prop, "270") == 0)
result = VIDEO_LAYOUT_ROT270;
}
if ((prop = scope_eval(scope, rxml_node_attrib(node, "swapxy"))))
{
if (strcmp(prop, "no") != 0)
result ^= VIDEO_LAYOUT_SWAP_XY;
}
if ((prop = scope_eval(scope, rxml_node_attrib(node, "flipx"))))
{
if (strcmp(prop, "no") != 0)
result ^= VIDEO_LAYOUT_FLIP_X;
}
if ((prop = scope_eval(scope, rxml_node_attrib(node, "flipy"))))
{
if (strcmp(prop, "no") != 0)
result ^= VIDEO_LAYOUT_FLIP_Y;
}
return result;
}
static bool load_param(scope_t *scope, rxml_node_t *node, bool can_repeat)
{
const char *name;
const char *value;
const char *start;
if (!(name = rxml_node_attrib(node, "name")))
{
RARCH_LOG("video_layout: <param> is missing 'name' attribute\n");
return false;
}
value = rxml_node_attrib(node, "value");
start = rxml_node_attrib(node, "start");
if (can_repeat && start)
{
const char *inc = rxml_node_attrib(node, "increment");
const char *ls = rxml_node_attrib(node, "lshift");
const char *rs = rxml_node_attrib(node, "rshift");
if (inc || ls || rs)
{
scope_generator(scope, name, start, inc, ls, rs);
}
else
{
RARCH_LOG("video_layout: invalid generator <param name=\"%s\" /> missing increment/shift\n",
scope_eval(scope, name));
return false;
}
}
else if (name && value)
{
scope_param(scope, name, value);
}
else
{
RARCH_LOG("video_layout: invalid parameter <param name=\"%s\" /> missing value\n",
scope_eval(scope, name));
return false;
}
return true;
}
static bool load_component(scope_t *scope, component_t *comp, rxml_node_t *node)
{
comp_type_t type;
bool result;
const char *state;
const char *attr;
rxml_node_t *n;
type = comp_type_from_str(node->name);
result = true;
if (type == VIDEO_LAYOUT_C_UNKNOWN)
{
RARCH_LOG("video_layout: invalid component <%s />\n", node->name);
return false;
}
component_init(comp, type);
if ((state = rxml_node_attrib(node, "state")))
comp->enabled_state = get_int(scope_eval(scope, state));
for (n = node->children; n; n = n->next)
{
if (strcmp(n->name, "bounds") == 0)
comp->bounds = parse_bounds(scope, n);
else if (strcmp(n->name, "color") == 0)
comp->color = parse_color(scope, n);
}
switch (comp->type)
{
case VIDEO_LAYOUT_C_UNKNOWN:
break;
case VIDEO_LAYOUT_C_SCREEN:
break;
case VIDEO_LAYOUT_C_RECT:
break;
case VIDEO_LAYOUT_C_DISK:
break;
case VIDEO_LAYOUT_C_IMAGE:
{
if (!(attr = rxml_node_attrib(node, "file")))
{
RARCH_LOG("video_layout: invalid component <%s />, missing 'file' attribute\n", node->name);
result = false;
}
set_string(&comp->attr.image.file, scope_eval(scope, attr));
if ((attr = rxml_node_attrib(node, "alphafile")))
set_string(&comp->attr.image.alpha_file, scope_eval(scope, attr));
}
break;
case VIDEO_LAYOUT_C_TEXT:
{
if (!(attr = rxml_node_attrib(node, "string")))
{
RARCH_LOG("video_layout: invalid component <%s />, missing 'string' attribute\n", node->name);
result = false;
}
set_string(&comp->attr.text.string, scope_eval(scope, attr));
if ((attr = rxml_node_attrib(node, "align")))
comp->attr.text.align = (video_layout_text_align_t)get_int(scope_eval(scope, attr));
}
break;
case VIDEO_LAYOUT_C_COUNTER:
{
if ((attr = rxml_node_attrib(node, "digits")))
comp->attr.counter.digits = get_int(scope_eval(scope, attr));
if ((attr = rxml_node_attrib(node, "maxstate")))
comp->attr.counter.max_state = get_int(scope_eval(scope, attr));
if ((attr = rxml_node_attrib(node, "align")))
comp->attr.counter.align = (video_layout_text_align_t)get_int(scope_eval(scope, attr));
}
break;
case VIDEO_LAYOUT_C_DOTMATRIX_X1:
break;
case VIDEO_LAYOUT_C_DOTMATRIX_H5:
break;
case VIDEO_LAYOUT_C_DOTMATRIX_H8:
break;
case VIDEO_LAYOUT_C_LED_7:
break;
case VIDEO_LAYOUT_C_LED_8_GTS1:
break;
case VIDEO_LAYOUT_C_LED_14:
break;
case VIDEO_LAYOUT_C_LED_14_SC:
break;
case VIDEO_LAYOUT_C_LED_16:
break;
case VIDEO_LAYOUT_C_LED_16_SC:
break;
case VIDEO_LAYOUT_C_REEL:
break;
}
return result;
}
static bool load_element(scope_t *scope, rxml_node_t *node)
{
const char *name;
const char *state;
bool result;
int i;
element_t *elem;
rxml_node_t *n;
video_layout_bounds_t dim;
result = true;
if (!(name = rxml_node_attrib(node, "name")))
{
RARCH_LOG("video_layout: <element> is missing 'name' attribute\n");
return false;
}
elem = scope_add_element(scope);
element_init(elem, scope_eval(scope, name), child_count(node));
if ((state = rxml_node_attrib(node, "defstate")))
elem->state = get_int(scope_eval(scope, state));
i = 0;
for (n = node->children; n; n = n->next, ++i)
{
component_t *comp;
comp = &elem->components[i];
if (load_component(scope, comp, n))
elem->bounds = bounds_union(&elem->bounds, &comp->bounds);
else
result = false;
}
if (bounds_valid(&elem->bounds))
{
dim.x = elem->bounds.x / elem->bounds.w;
dim.y = elem->bounds.y / elem->bounds.h;
dim.w = 1.0f / elem->bounds.w;
dim.h = 1.0f / elem->bounds.h;
}
else
{
dim = make_bounds_unit();
}
for (i = 0; i < elem->components_count; ++i)
{
component_t *comp;
comp = &elem->components[i];
if (bounds_valid(&comp->bounds))
bounds_scale(&comp->bounds, &dim);
else
comp->bounds = dim;
comp->bounds.x -= dim.x;
comp->bounds.y -= dim.y;
}
elem->bounds = make_bounds_unit();
return result;
}
static bool load_screen(scope_t *scope, element_t *elem, rxml_node_t *node)
{
const char *index;
component_t *comp;
index = rxml_node_attrib(node, "index");
element_init(elem, NULL, 1);
comp = &elem->components[0];
component_init(comp, VIDEO_LAYOUT_C_SCREEN);
comp->bounds = make_bounds_unit();
comp->attr.screen.index = get_int(scope_eval(scope, index));
return true;
}
static void merge_group(scope_t *scope, view_t *view, view_t *group,
bool has_bounds, video_layout_bounds_t n_bounds, video_layout_orientation_t n_orient, video_layout_color_t n_color)
{
bool constrain;
int i, j, k;
constrain = bounds_valid(&n_bounds);
for (i = 0; i < group->layers_count; ++i)
{
layer_t *group_layer;
layer_t *layer;
group_layer = &group->layers[i];
layer = view_emplace_layer(view, group_layer->name);
for (j = 0; j < group_layer->elements_count; ++j)
{
element_t *elem;
elem = layer_add_element(layer);
element_copy(elem, &group_layer->elements[j]);
for (k = 0; k < elem->components_count; ++k)
color_mod(&elem->components->color, &n_color);
if (n_orient)
element_apply_orientation(elem, n_orient);
if (constrain)
{
bounds_scale(&elem->bounds, &n_bounds);
elem->bounds.x += n_bounds.x;
elem->bounds.y += n_bounds.y;
}
if (!has_bounds)
view->bounds = bounds_union(&view->bounds, &elem->bounds);
}
}
}
static bool load_view(scope_t *scope, view_t *view, rxml_node_t *node, bool is_named)
{
bool result, has_bounds;
rxml_node_t *n;
rxml_node_t *o;
int i;
if (is_named)
{
const char *name;
if (!(name = rxml_node_attrib(node, "name")))
{
RARCH_LOG("video_layout: <view> is missing 'name' attribute\n");
return false;
}
view_init(view, scope_eval(scope, name));
}
result = true;
has_bounds = false;
for (n = node->children; n; n = n->next)
{
video_layout_color_t n_color;
video_layout_bounds_t n_bounds;
video_layout_orientation_t n_orient;
if (strcmp(n->name, "param") == 0)
{
if (!load_param(scope, n, true))
result = false;
continue;
}
else if (strcmp(n->name, "bounds") == 0)
{
view->bounds = parse_bounds(scope, n);
has_bounds = true;
continue;
}
n_color = make_color_white();
n_bounds = make_bounds();
n_orient = VIDEO_LAYOUT_ROT0;
for (o = n->children; o; o = o->next)
{
if (strcmp(o->name, "color") == 0)
n_color = parse_color(scope, o);
else if (strcmp(o->name, "bounds") == 0)
n_bounds = parse_bounds(scope, o);
else if (strcmp(o->name, "orientation") == 0)
n_orient = parse_orientation(scope, o);
}
if (strcmp(n->name, "group") == 0)
{
const char *ref;
if ((ref = rxml_node_attrib(n, "ref")))
{
view_t *group;
if ((group = scope_find_group(scope, scope_eval(scope, ref))))
{
merge_group(scope, view, group, has_bounds, n_bounds, n_orient, n_color);
}
else
{
RARCH_LOG("video_layout: group \"%s\" is missing\n", scope_eval(scope, ref));
result = false;
}
}
else
{
RARCH_LOG("video_layout: <group> is missing 'ref' attribute\n");
result = false;
}
}
else if (strcmp(n->name, "repeat") == 0)
{
const char *count_s;
int count;
if (!(count_s = rxml_node_attrib(n, "count")))
{
RARCH_LOG("video_layout: <repeat> is missing 'count' attribute\n");
result = false;
continue;
}
count = get_int(scope_eval(scope, count_s));
scope_push(scope);
for (o = n->children; o; o = o->next)
{
if (strcmp(o->name, "param") == 0)
{
if (!load_param(scope, o, true))
result = false;
}
}
for (i = 0; i < count; ++i)
{
view_t rep;
view_init(&rep, NULL);
if (!load_view(scope, &rep, n, false))
result = false;
merge_group(scope, view, &rep, has_bounds, n_bounds, n_orient, n_color);
view_deinit(&rep);
scope_repeat(scope);
}
scope_pop(scope);
}
else /* element */
{
layer_t *layer;
element_t *elem;
layer = view_emplace_layer(view, n->name);
elem = layer_add_element(layer);
if (strcmp(n->name, "screen") == 0)
{
if (!load_screen(scope, elem, n))
result = false;
}
else
{
const char *elem_name;
const char *attr;
if ((elem_name = rxml_node_attrib(n, "element")))
{
element_t *elem_src;
if ((elem_src = scope_find_element(scope, elem_name)))
{
element_copy(elem, elem_src);
if ((attr = rxml_node_attrib(n, "name")))
elem->o_bind = video_layout_io_find(scope_eval(scope, attr));
if ((attr = rxml_node_attrib(n, "inputtag")))
elem->i_bind = video_layout_io_find(scope_eval(scope, attr));
if ((attr = rxml_node_attrib(n, "inputmask")))
elem->i_mask = get_int(scope_eval(scope, attr));
if ((attr = rxml_node_attrib(n, "inputraw")))
elem->i_raw = get_int(scope_eval(scope, attr)) ? true : false;
}
else
{
RARCH_LOG("video_layout: element \"%s\" is missing\n", scope_eval(scope, elem_name));
result = false;
}
}
else
{
RARCH_LOG("video_layout: <%s> is missing 'element' attribute\n", n->name);
result = false;
}
}
for (i = 0; i < elem->components_count; ++i)
color_mod(&elem->components->color, &n_color);
elem->bounds = n_bounds;
if (n_orient)
element_apply_orientation(elem, n_orient);
if (!has_bounds)
view->bounds = bounds_union(&view->bounds, &elem->bounds);
}
}
return result;
}
static bool load_group(scope_t *scope, rxml_node_t *node)
{
bool result = true;
view_t *group = scope_add_group(scope);
scope_push(scope);
if (!load_view(scope, group, node, true))
result = false;
scope_pop(scope);
return result;
}
static bool load_top_level(scope_t *scope, int *view_count, rxml_node_t *root)
{
bool result;
rxml_node_t *node;
result = true;
*view_count = 0;
for (node = root->children; node; node = node->next)
{
if (strcmp(node->name, "param") == 0)
{
if (!load_param(scope, node, false))
result = false;
}
else if (strcmp(node->name, "element") == 0)
{
if (!load_element(scope, node))
result = false;
}
else if (strcmp(node->name, "group") == 0)
{
if (!load_group(scope, node))
result = false;
}
else if (strcmp(node->name, "view") == 0)
++(*view_count);
}
return result;
}
static bool load_views(scope_t *scope, view_array_t *view_array, rxml_node_t *root)
{
bool result;
int i;
rxml_node_t *node;
result = true;
i = 0;
for (node = root->children; node; node = node->next)
{
if (strcmp(node->name, "view") == 0)
{
view_t *view;
view = &view_array->views[i];
scope_push(scope);
if (!load_view(scope, view, node, true))
result = false;
view_sort_layers(view);
view_normalize(view);
view_count_screens(view);
scope_pop(scope);
++i;
}
}
return result;
}
bool load(view_array_t *view_array, rxml_document_t *doc)
{
bool result;
rxml_node_t *root;
scope_t scope;
int view_count;
root = rxml_root_node(doc);
if (strcmp(root->name, "mamelayout") ||
strcmp(rxml_node_attrib(root, "version"), "2"))
{
RARCH_LOG("video_layout: invalid MAME Layout file\n");
return false;
}
result = false;
scope_init(&scope);
init_device_params(&scope);
init_screen_params(&scope, 0);
init_screen_params(&scope, 1);
if (!load_top_level(&scope, &view_count, root))
result = false;
view_array_init(view_array, view_count);
if (!load_views(&scope, view_array, root))
result = false;
scope_deinit(&scope);
return result;
}