diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 54a9a71f38..369664fa43 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -842,6 +842,10 @@ static void video_driver_free_internal(void) bool is_threaded = video_driver_is_threaded_internal(); #endif +#ifdef HAVE_VIDEO_LAYOUT + video_layout_deinit(); +#endif + command_event(CMD_EVENT_OVERLAY_DEINIT, NULL); if (!video_driver_is_video_cache_context()) @@ -1099,6 +1103,15 @@ static bool video_driver_init_internal(bool *video_is_threaded) command_event(CMD_EVENT_OVERLAY_DEINIT, NULL); command_event(CMD_EVENT_OVERLAY_INIT, NULL); +#ifdef HAVE_VIDEO_LAYOUT + if(settings->bools.video_layout_enable) + { + video_layout_init(video_driver_data, video_driver_layout_render_interface()); + video_layout_load(settings->paths.path_video_layout); + video_layout_view_select(settings->uints.video_layout_selected_view); + } +#endif + if (!core_is_game_loaded()) video_driver_cached_frame_set(&dummy_pixels, 4, 4, 8); diff --git a/gfx/video_layout.c b/gfx/video_layout.c new file mode 100644 index 0000000000..da55da3671 --- /dev/null +++ b/gfx/video_layout.c @@ -0,0 +1,470 @@ +#include "video_layout.h" +#include "video_layout/view.h" +#include "video_driver.h" +#include +#include +#include +#include +#include +#include +#include + +bool load(view_array_t *view_array, rxml_document_t *doc); + +typedef struct io +{ + char *name; + int base_value; + int value; +} +io_t; + +typedef struct video_layout_state +{ + video_layout_render_info_t render_info; + const video_layout_render_interface_t *render; + + view_array_t view_array; + + view_t *view; + int view_index; + + io_t *io; + int io_count; + + void **images; + int images_count; + + char *base_path; + + bool is_archive; + bool view_changed; +} +video_layout_state_t; + +static video_layout_state_t *video_layout_state = NULL; + +void video_layout_init(void *video_driver_data, const video_layout_render_interface_t *render) +{ + if (video_layout_state) + video_layout_deinit(); + + video_layout_state = (video_layout_state_t*)calloc(1, sizeof(video_layout_state_t)); + video_layout_state->render_info.video_driver_data = video_driver_data; + video_layout_state->render = render; + + vec_size((void**)&video_layout_state->images, sizeof(void*), 1); + + video_layout_state->images[0] = NULL; + video_layout_state->images_count = 1; +} + +void video_layout_deinit(void) +{ + int i; + + if (!video_layout_state) + return; + + free(video_layout_state->base_path); + + for (i = 1; i < video_layout_state->images_count; ++i) + { + video_layout_state->render->free_image( + video_layout_state->render_info.video_driver_data, + video_layout_state->images[i] + ); + } + + free(video_layout_state->images); + + for (i = 0; i < video_layout_state->io_count; ++i) + free(video_layout_state->io[i].name); + + free(video_layout_state->io); + + view_array_deinit(&video_layout_state->view_array); + + free(video_layout_state); + video_layout_state = NULL; +} + +int video_layout_io_assign(const char *name, int base_value) +{ + int index; + + index = video_layout_state->io_count; + + vec_size((void**)&video_layout_state->io, sizeof(io_t), ++video_layout_state->io_count); + + video_layout_state->io[index].name = init_string(name); + video_layout_state->io[index].base_value = base_value; + video_layout_state->io[index].value = base_value; + + return index; +} + +int video_layout_io_find(const char *name) +{ + int i; + + for (i = 0; i < video_layout_state->io_count; ++i) + { + if (strcmp(video_layout_state->io[i].name, name) == 0) + return i; + } + + return -1; +} + +int video_layout_io_get(int index) +{ + return video_layout_state->io[index].value; +} + +void video_layout_io_set(int index, int value) +{ + video_layout_state->io[index].value = value; +} + +bool video_layout_load(const char *path) +{ + rxml_document_t *doc; + bool result; + + if(!path || !strlen(path)) + return true; + + video_layout_state->is_archive = path_is_compressed_file(path); + + doc = NULL; + + if(video_layout_state->is_archive) + { + void *buf; + int64_t len; + + char respath[PATH_MAX_LENGTH]; + strlcpy(respath, path, sizeof(respath)); + strlcat(respath, "#", sizeof(respath)); + set_string(&video_layout_state->base_path, respath); + + strlcat(respath, "default.lay", sizeof(respath)); + if (file_archive_compressed_read(respath, &buf, NULL, &len)) + { + char *str; + if ((str = (char*)realloc(buf, (size_t)len + 1))) + { + str[(size_t)len] = '\0'; + doc = rxml_load_document_string(str); + free(str); + } + else free(buf); + } + } + else + { + char respath[PATH_MAX_LENGTH]; + fill_pathname_basedir(respath, path, sizeof(respath)); + set_string(&video_layout_state->base_path, respath); + doc = rxml_load_document(path); + } + + if (!doc) + { + RARCH_LOG("video_layout: unable to open file \"%s\"\n", path); + return false; + } + + result = load(&video_layout_state->view_array, doc); + rxml_free_document(doc); + + video_layout_view_select(video_layout_view_index()); + return result; +} + +bool video_layout_valid(void) +{ + return video_layout_state && video_layout_state->view; +} + +static int video_layout_load_image(const char *path) +{ + struct texture_image image; + void *handle; + int index; + + image.supports_rgba = video_driver_supports_rgba(); + + if (video_layout_state->is_archive) + { + void *buf; + int64_t len; + + char respath[PATH_MAX_LENGTH]; + strlcpy(respath, video_layout_state->base_path, sizeof(respath)); + strlcat(respath, path, sizeof(respath)); + + if (!file_archive_compressed_read(respath, &buf, NULL, &len)) + { + RARCH_LOG("video_layout: failed to decompress image: %s\n", respath); + return 0; + } + + if (!image_texture_load_buffer(&image, image_texture_get_type(path), buf, (size_t)len)) + { + free(buf); + + RARCH_LOG("video_layout: failed to load image: %s\n", respath); + return 0; + } + + free(buf); + } + else + { + char respath[PATH_MAX_LENGTH]; + strlcpy(respath, video_layout_state->base_path, sizeof(respath)); + strlcat(respath, path, sizeof(respath)); + + if (!image_texture_load(&image, respath)) + { + RARCH_LOG("video_layout: failed to load image: %s\n", respath); + return 0; + } + } + + handle = video_layout_state->render->take_image( + video_layout_state->render_info.video_driver_data, image); + + if (!handle) + return 0; + + index = video_layout_state->images_count; + + vec_size((void**)&video_layout_state->images, sizeof(void*), ++video_layout_state->images_count); + + video_layout_state->images[index] = handle; + + return index; +} + +int video_layout_view_count(void) +{ + return video_layout_state->view_array.views_count; +} + +const char *video_layout_view_name(int index) +{ + return video_layout_state->view_array.views[index].name; +} + +int video_layout_view_select(int index) +{ + index = MAX(0, MIN(index, video_layout_state->view_array.views_count - 1)); + + video_layout_state->view_index = index; + video_layout_state->view = video_layout_state->view_array.views_count ? + &video_layout_state->view_array.views[index] : NULL; + + video_layout_view_change(); + + return index; +} + +int video_layout_view_cycle(void) +{ + return video_layout_view_select( + (video_layout_state->view_index + 1) % video_layout_state->view_array.views_count); +} + +int video_layout_view_index(void) +{ + return video_layout_state->view_index; +} + +void video_layout_view_change(void) +{ + video_layout_state->view_changed = true; +} + +bool video_layout_view_on_change(void) +{ + if (video_layout_state->view_changed) + { + video_layout_state->view_changed = false; + return true; + } + return false; +} + +void video_layout_view_fit_bounds(video_layout_bounds_t bounds) +{ + view_t *view; + float c, dx, dy; + int i, j, k; + + view = video_layout_state->view; + + c = MIN(bounds.w / view->bounds.w, bounds.h / view->bounds.h); + + dx = view->bounds.w * c; + dy = view->bounds.h * c; + + view->render_bounds.w = dx; + view->render_bounds.h = dy; + view->render_bounds.x = (bounds.w - dx) / 2.f; + view->render_bounds.y = (bounds.h - dy) / 2.f; + + for (i = 0; i < view->layers_count; ++i) + { + layer_t *layer; + layer = &view->layers[i]; + + for (j = 0; j < layer->elements_count; ++j) + { + element_t *elem; + elem = &layer->elements[j]; + + elem->render_bounds.x = elem->bounds.x * view->render_bounds.w + view->render_bounds.x; + elem->render_bounds.y = elem->bounds.y * view->render_bounds.h + view->render_bounds.y; + elem->render_bounds.w = elem->bounds.w * view->render_bounds.w; + elem->render_bounds.h = elem->bounds.h * view->render_bounds.h; + + for (k = 0; k < elem->components_count; ++k) + { + component_t *comp; + comp = &elem->components[k]; + + comp->render_bounds.x = comp->bounds.x * elem->render_bounds.w + elem->render_bounds.x; + comp->render_bounds.y = comp->bounds.y * elem->render_bounds.h + elem->render_bounds.y; + comp->render_bounds.w = comp->bounds.w * elem->render_bounds.w; + comp->render_bounds.h = comp->bounds.h * elem->render_bounds.h; + + if (comp->type == VIDEO_LAYOUT_C_SCREEN) + view->screens[comp->attr.screen.index] = comp->render_bounds; + } + } + } +} + +int video_layout_layer_count(void) +{ + return video_layout_state->view->layers_count; +} + +void video_layout_layer_render(void *video_driver_frame_data, int index) +{ + video_layout_render_info_t *info; + const video_layout_render_interface_t *r; + layer_t *layer; + int i, j; + + info = &video_layout_state->render_info; + r = video_layout_state->render; + layer = &video_layout_state->view->layers[index]; + + info->video_driver_frame_data = video_driver_frame_data; + + r->layer_begin(info); + + for (i = 0; i < layer->elements_count; ++i) + { + element_t *elem; + elem = &layer->elements[i]; + + if (elem->o_bind != -1) + elem->state = video_layout_state->io[elem->o_bind].value; + + for (j = 0; j < elem->components_count; ++j) + { + component_t *comp; + comp = &elem->components[j]; + + if (comp->enabled_state != -1) + { + if(comp->enabled_state != elem->state) + continue; + } + + info->bounds = comp->render_bounds; + info->orientation = comp->orientation; + info->color = comp->color; + + switch (comp->type) + { + case VIDEO_LAYOUT_C_UNKNOWN: + break; + case VIDEO_LAYOUT_C_SCREEN: + r->screen(info, comp->attr.screen.index); + break; + case VIDEO_LAYOUT_C_RECT: + r->rect(info); + break; + case VIDEO_LAYOUT_C_DISK: + r->ellipse(info); + break; + case VIDEO_LAYOUT_C_IMAGE: + if(!comp->attr.image.loaded) + { + comp->attr.image.image_idx = video_layout_load_image(comp->attr.image.file); + if(comp->attr.image.alpha_file) + comp->attr.image.alpha_idx = video_layout_load_image(comp->attr.image.alpha_file); + comp->attr.image.loaded = true; + } + r->image(info, + video_layout_state->images[comp->attr.image.image_idx], + video_layout_state->images[comp->attr.image.alpha_idx]); + break; + case VIDEO_LAYOUT_C_TEXT: + r->text(info, comp->attr.text.string); + break; + case VIDEO_LAYOUT_C_COUNTER: + r->counter(info, MIN(elem->state, comp->attr.counter.max_state)); + break; + case VIDEO_LAYOUT_C_DOTMATRIX_X1: + r->led_dot(info, 1, elem->state); + break; + case VIDEO_LAYOUT_C_DOTMATRIX_H5: + r->led_dot(info, 5, elem->state); + break; + case VIDEO_LAYOUT_C_DOTMATRIX_H8: + r->led_dot(info, 8, elem->state); + break; + case VIDEO_LAYOUT_C_LED_7: + r->led_seg(info, VIDEO_LAYOUT_LED_7, elem->state); + break; + case VIDEO_LAYOUT_C_LED_8_GTS1: + r->led_seg(info, VIDEO_LAYOUT_LED_8_GTS1, elem->state); + break; + case VIDEO_LAYOUT_C_LED_14: + r->led_seg(info, VIDEO_LAYOUT_LED_14, elem->state); + break; + case VIDEO_LAYOUT_C_LED_14_SC: + r->led_seg(info, VIDEO_LAYOUT_LED_14_SC, elem->state); + break; + case VIDEO_LAYOUT_C_LED_16: + r->led_seg(info, VIDEO_LAYOUT_LED_16, elem->state); + break; + case VIDEO_LAYOUT_C_LED_16_SC: + r->led_seg(info, VIDEO_LAYOUT_LED_16_SC, elem->state); + break; + case VIDEO_LAYOUT_C_REEL: + /* not implemented */ + break; + } + } + } + + r->layer_end(info, layer->blend); +} + +const video_layout_bounds_t *video_layout_screen(int index) +{ + return &video_layout_state->view->screens[index]; +} + +int video_layout_screen_count(void) +{ + return video_layout_state->view->screens_count; +} diff --git a/gfx/video_layout.h b/gfx/video_layout.h new file mode 100644 index 0000000000..8979e20329 --- /dev/null +++ b/gfx/video_layout.h @@ -0,0 +1,77 @@ +#ifndef VIDEO_LAYOUT_H +#define VIDEO_LAYOUT_H +#include "video_layout/types.h" +#include +#include + +typedef struct video_layout_render_info +{ + void *video_driver_data; + void *video_driver_frame_data; + + video_layout_bounds_t bounds; + video_layout_orientation_t orientation; + video_layout_color_t color; +} +video_layout_render_info_t; + +typedef enum video_layout_led +{ + VIDEO_LAYOUT_LED_7, /* digit with . */ + VIDEO_LAYOUT_LED_8_GTS1, /* digit with vertical split */ + VIDEO_LAYOUT_LED_14, /* alphanumeric */ + VIDEO_LAYOUT_LED_14_SC, /* alphanumeric with ., */ + VIDEO_LAYOUT_LED_16, /* full alphanumeric */ + VIDEO_LAYOUT_LED_16_SC /* full alphanumeric with ., */ +} +video_layout_led_t; + +typedef struct video_layout_render_interface +{ + void *(*take_image) (void *video_driver_data, struct texture_image image); + void (*free_image) (void *video_driver_data, void *image); + + void (*layer_begin) (const video_layout_render_info_t *info); + + void (*screen) (const video_layout_render_info_t *info, int screen_index); + void (*image) (const video_layout_render_info_t *info, void *image_handle, void *alpha_handle); + void (*text) (const video_layout_render_info_t *info, const char *str); + void (*counter) (const video_layout_render_info_t *info, int value); + void (*rect) (const video_layout_render_info_t *info); + void (*ellipse) (const video_layout_render_info_t *info); + void (*led_dot) (const video_layout_render_info_t *info, int dot_count, int dot_mask); + void (*led_seg) (const video_layout_render_info_t *info, video_layout_led_t seg_layout, int seg_mask); + + void (*layer_end) (const video_layout_render_info_t *info, video_layout_blend_t blend_type); +} +video_layout_render_interface_t; + +void video_layout_init (void *video_driver_data, const video_layout_render_interface_t *render); +void video_layout_deinit (void); + +int video_layout_io_assign (const char *name, int base_value); +int video_layout_io_get (int index); +void video_layout_io_set (int index, int value); + +bool video_layout_load (const char *path); +bool video_layout_valid (void); + +int video_layout_view_count (void); +const char *video_layout_view_name (int index); + +int video_layout_view_select (int index); +int video_layout_view_cycle (void); +int video_layout_view_index (void); + +void video_layout_view_change (void); +bool video_layout_view_on_change (void); +void video_layout_view_fit_bounds (video_layout_bounds_t bounds); + +int video_layout_layer_count (void); +void video_layout_layer_render (void *video_driver_frame_data, int index); + +const video_layout_bounds_t + *video_layout_screen (int index); +int video_layout_screen_count (void); + +#endif diff --git a/gfx/video_layout/component.c b/gfx/video_layout/component.c new file mode 100644 index 0000000000..08708d00bc --- /dev/null +++ b/gfx/video_layout/component.c @@ -0,0 +1,165 @@ +#include "component.h" +#include +#include + +void component_init(component_t *comp, comp_type_t type) +{ + comp->type = type; + comp->bounds = make_bounds(); + comp->render_bounds = make_bounds_unit(); + comp->orientation = VIDEO_LAYOUT_ROT0; + comp->color = make_color_white(); + comp->enabled_state = -1; + + switch (comp->type) + { + case VIDEO_LAYOUT_C_UNKNOWN: + break; + case VIDEO_LAYOUT_C_SCREEN: + comp->attr.screen.index = 0; + break; + case VIDEO_LAYOUT_C_RECT: + break; + case VIDEO_LAYOUT_C_DISK: + break; + case VIDEO_LAYOUT_C_IMAGE: + comp->attr.image.file = NULL; + comp->attr.image.alpha_file = NULL; + comp->attr.image.image_idx = 0; + comp->attr.image.alpha_idx = 0; + comp->attr.image.loaded = false; + break; + case VIDEO_LAYOUT_C_TEXT: + comp->attr.text.string = NULL; + comp->attr.text.align = VIDEO_LAYOUT_TEXT_ALIGN_CENTER; + break; + case VIDEO_LAYOUT_C_COUNTER: + comp->attr.counter.digits = 2; + comp->attr.counter.max_state = 999; + comp->attr.counter.align = VIDEO_LAYOUT_TEXT_ALIGN_CENTER; + 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; + } +} + +void component_copy(component_t *comp, const component_t *src) +{ + comp->type = src->type; + comp->bounds = src->bounds; + comp->render_bounds = src->render_bounds; + comp->orientation = src->orientation; + comp->color = src->color; + comp->enabled_state = src->enabled_state; + + switch (comp->type) + { + case VIDEO_LAYOUT_C_UNKNOWN: + break; + case VIDEO_LAYOUT_C_SCREEN: + comp->attr.screen.index = src->attr.screen.index; + break; + case VIDEO_LAYOUT_C_RECT: + break; + case VIDEO_LAYOUT_C_DISK: + break; + case VIDEO_LAYOUT_C_IMAGE: + comp->attr.image.file = init_string(src->attr.image.file); + comp->attr.image.alpha_file = init_string(src->attr.image.alpha_file); + comp->attr.image.image_idx = src->attr.image.image_idx; + comp->attr.image.alpha_idx = src->attr.image.alpha_idx; + comp->attr.image.loaded = src->attr.image.loaded; + break; + case VIDEO_LAYOUT_C_TEXT: + comp->attr.text.string = init_string(src->attr.text.string); + comp->attr.text.align = src->attr.text.align; + break; + case VIDEO_LAYOUT_C_COUNTER: + comp->attr.counter.digits = src->attr.counter.digits; + comp->attr.counter.max_state = src->attr.counter.max_state; + comp->attr.counter.align = src->attr.counter.align; + 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; + } +} + +void component_deinit(component_t *comp) +{ + 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: + free(comp->attr.image.file); + free(comp->attr.image.alpha_file); + break; + case VIDEO_LAYOUT_C_TEXT: + free(comp->attr.text.string); + break; + case VIDEO_LAYOUT_C_COUNTER: + 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; + } +} diff --git a/gfx/video_layout/component.h b/gfx/video_layout/component.h new file mode 100644 index 0000000000..a6797964a7 --- /dev/null +++ b/gfx/video_layout/component.h @@ -0,0 +1,53 @@ +#ifndef VIDEO_LAYOUT_COMPONENT_H +#define VIDEO_LAYOUT_COMPONENT_H + +#include "internal.h" +#include "component_attr.h" + +typedef enum comp_type +{ + VIDEO_LAYOUT_C_UNKNOWN, + VIDEO_LAYOUT_C_SCREEN, + VIDEO_LAYOUT_C_RECT, + VIDEO_LAYOUT_C_DISK, + VIDEO_LAYOUT_C_IMAGE, + VIDEO_LAYOUT_C_TEXT, + VIDEO_LAYOUT_C_COUNTER, + VIDEO_LAYOUT_C_DOTMATRIX_X1, + VIDEO_LAYOUT_C_DOTMATRIX_H5, + VIDEO_LAYOUT_C_DOTMATRIX_H8, + VIDEO_LAYOUT_C_LED_7, + VIDEO_LAYOUT_C_LED_8_GTS1, + VIDEO_LAYOUT_C_LED_14, + VIDEO_LAYOUT_C_LED_14_SC, + VIDEO_LAYOUT_C_LED_16, + VIDEO_LAYOUT_C_LED_16_SC, + VIDEO_LAYOUT_C_REEL +} +comp_type_t; + +union comp_attr +{ + c_attr_screen_t screen; + c_attr_image_t image; + c_attr_text_t text; + c_attr_counter_t counter; +}; + +typedef struct component +{ + comp_type_t type; + video_layout_bounds_t bounds; + video_layout_bounds_t render_bounds; + video_layout_orientation_t orientation; + video_layout_color_t color; + int enabled_state; + union comp_attr attr; +} +component_t; + +void component_init (component_t *comp, comp_type_t type); +void component_copy (component_t *comp, const component_t *src); +void component_deinit (component_t *comp); + +#endif diff --git a/gfx/video_layout/component_attr.h b/gfx/video_layout/component_attr.h new file mode 100644 index 0000000000..703e093ce6 --- /dev/null +++ b/gfx/video_layout/component_attr.h @@ -0,0 +1,35 @@ +#ifndef VIDEO_LAYOUT_COMPONENT_ATTR_H +#define VIDEO_LAYOUT_COMPONENT_ATTR_H + +typedef struct c_attr_screen +{ + int index; +} +c_attr_screen_t; + +typedef struct c_attr_image +{ + char *file; + char *alpha_file; + int image_idx; + int alpha_idx; + bool loaded; +} +c_attr_image_t; + +typedef struct c_attr_text +{ + char *string; + video_layout_text_align_t align; +} +c_attr_text_t; + +typedef struct c_attr_counter +{ + int digits; + int max_state; + video_layout_text_align_t align; +} +c_attr_counter_t; + +#endif diff --git a/gfx/video_layout/element.c b/gfx/video_layout/element.c new file mode 100644 index 0000000000..9f79edf641 --- /dev/null +++ b/gfx/video_layout/element.c @@ -0,0 +1,78 @@ +#include "element.h" +#include + +void element_init(element_t *elem, const char *name, int components_count) +{ + elem->name = init_string(name); + elem->state = -1; + elem->o_bind = -1; + elem->i_bind = -1; + elem->i_mask = -1; + elem->i_raw = false; + + elem->bounds = make_bounds(); + elem->render_bounds = make_bounds_unit(); + + elem->components = (component_t*)(components_count > 0 ? + calloc(components_count, sizeof(component_t)) : NULL); + elem->components_count = components_count; +} + +void element_copy(element_t *elem, const element_t *src) +{ + int i; + + elem->name = init_string(src->name); + elem->state = src->state; + + elem->bounds = src->bounds; + elem->render_bounds = src->render_bounds; + + elem->components = (component_t*)(src->components_count > 0 ? + calloc(src->components_count, sizeof(component_t)) : NULL); + + for (i = 0; i < src->components_count; ++i) + component_copy(&elem->components[i], &src->components[i]); + + elem->components_count = src->components_count; +} + +void element_deinit(element_t *elem) +{ + int i; + + for (i = 0; i < elem->components_count; ++i) + component_deinit(&elem->components[i]); + free(elem->components); + + free(elem->name); +} + +void element_apply_orientation(element_t *elem, video_layout_orientation_t orientation) +{ + int i; + + for (i = 0; i < elem->components_count; ++i) + { + component_t *comp; + comp = &elem->components[i]; + comp->orientation ^= orientation; + + if (orientation & VIDEO_LAYOUT_SWAP_XY) + { + video_layout_bounds_t b; + b = comp->bounds; + + comp->bounds.x = b.y; + comp->bounds.y = b.x; + comp->bounds.w = b.h; + comp->bounds.h = b.w; + } + + if (orientation & VIDEO_LAYOUT_FLIP_X) + comp->bounds.x = 1.0f - comp->bounds.x - comp->bounds.w; + + if (orientation & VIDEO_LAYOUT_FLIP_Y) + comp->bounds.y = 1.0f - comp->bounds.y - comp->bounds.h; + } +} diff --git a/gfx/video_layout/element.h b/gfx/video_layout/element.h new file mode 100644 index 0000000000..52e12940cb --- /dev/null +++ b/gfx/video_layout/element.h @@ -0,0 +1,28 @@ +#ifndef VIDEO_LAYOUT_ELEMENT_H +#define VIDEO_LAYOUT_ELEMENT_H +#include "internal.h" +#include "component.h" + +typedef struct element +{ + char *name; + int state; + int o_bind; + int i_bind; + int i_mask; + bool i_raw; + + video_layout_bounds_t bounds; + video_layout_bounds_t render_bounds; + + component_t *components; + int components_count; +} +element_t; + +void element_init (element_t *elem, const char *name, int components_count); +void element_copy (element_t *elem, const element_t *src); +void element_deinit (element_t *elem); +void element_apply_orientation (element_t *elem, video_layout_orientation_t orientation); + +#endif diff --git a/gfx/video_layout/internal.c b/gfx/video_layout/internal.c new file mode 100644 index 0000000000..78289bb316 --- /dev/null +++ b/gfx/video_layout/internal.c @@ -0,0 +1,181 @@ +#include "internal.h" +#include +#include +#include +#include + +char *init_string(const char *src) +{ + return src ? strdup(src) : NULL; +} + +void set_string(char **string, const char *src) +{ + free(*string); + *string = src ? strdup(src) : NULL; +} + +bool vec_size(void **target, size_t elem_size, int count) +{ + const int seg = 4; + + if (--count % seg == 0) + { + void *resized = realloc(*target, elem_size * (count + seg)); + if (!resized) + return false; + *target = resized; + } + + return true; +} + +bool is_decimal(const char *str) +{ + float v; + + v = 0.0f; + sscanf(str, "%f", &v); + return (v && v != (int)v); +} + +int get_int(const char *str) +{ + int res; + + res = 0; + + if (str[0] == '#') + ++str; + + if (str[0] == '$') + { + unsigned hex; + + ++str; + sscanf(str, "%x", &hex); + res = (int)hex; + } + else + { + sscanf(str, "%i", &res); + } + + return res; +} + +float get_dec(const char *str) +{ + float res; + + res = 0.0f; + sscanf(str, "%f", &res); + + return res; +} + +video_layout_color_t make_color(void) +{ + video_layout_color_t color; + color.r = 0.0f; + color.g = 0.0f; + color.b = 0.0f; + color.a = 0.0f; + return color; +} + +video_layout_color_t make_color_white(void) +{ + video_layout_color_t color; + color.r = 1.0f; + color.g = 1.0f; + color.b = 1.0f; + color.a = 1.0f; + return color; +} + +video_layout_color_t make_color_v(float v) +{ + video_layout_color_t color; + color.r = v; + color.g = v; + color.b = v; + color.a = 1.0f; + return color; +} + +video_layout_color_t make_color_rgb(float r, float g, float b) +{ + video_layout_color_t color; + color.r = r; + color.g = g; + color.b = b; + color.a = 1.0f; + return color; +} + +video_layout_color_t make_color_rgba(float r, float g, float b, float a) +{ + video_layout_color_t color; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return color; +} + +void color_mod(video_layout_color_t *dst, const video_layout_color_t *src) +{ + dst->r *= src->r; + dst->g *= src->g; + dst->b *= src->b; + dst->a *= src->a; +} + +video_layout_bounds_t make_bounds(void) +{ + video_layout_bounds_t bounds; + bounds.x = 0.0f; + bounds.y = 0.0f; + bounds.w = 0.0f; + bounds.h = 0.0f; + return bounds; +} + +video_layout_bounds_t make_bounds_unit(void) +{ + video_layout_bounds_t bounds; + bounds.x = 0.0f; + bounds.y = 0.0f; + bounds.w = 1.0f; + bounds.h = 1.0f; + return bounds; +} + +video_layout_bounds_t bounds_union(const video_layout_bounds_t *a, const video_layout_bounds_t *b) +{ + video_layout_bounds_t bounds; + + if (!bounds_valid(a)) return *b; + if (!bounds_valid(b)) return *a; + + bounds.x = MIN(a->x, b->x); + bounds.y = MIN(a->y, b->y); + bounds.w = MAX(a->x + a->w, b->x + b->w) - bounds.x; + bounds.h = MAX(a->y + a->h, b->y + b->h) - bounds.y; + + return bounds; +} + +void bounds_scale(video_layout_bounds_t *dst, const video_layout_bounds_t *dim) +{ + dst->x *= dim->w; + dst->y *= dim->h; + dst->w *= dim->w; + dst->h *= dim->h; +} + +bool bounds_valid(const video_layout_bounds_t *bounds) +{ + return (bounds->w > 0 && bounds->h > 0); +} diff --git a/gfx/video_layout/internal.h b/gfx/video_layout/internal.h new file mode 100644 index 0000000000..0f8f4fb27c --- /dev/null +++ b/gfx/video_layout/internal.h @@ -0,0 +1,29 @@ +#ifndef VIDEO_LAYOUT_INTERNAL_H +#define VIDEO_LAYOUT_INTERNAL_H +#include "types.h" +#include +#include +#include + +char *init_string (const char *src); +void set_string (char **string, const char *src); +bool vec_size (void **target, size_t elem_size, int count); + +bool is_decimal (const char *str); +int get_int (const char *str); +float get_dec (const char *str); + +video_layout_color_t make_color (void); +video_layout_color_t make_color_white (void); +video_layout_color_t make_color_v (float v); +video_layout_color_t make_color_rgb (float r, float g, float b); +video_layout_color_t make_color_rgba (float r, float g, float b, float a); +void color_mod (video_layout_color_t *dst, const video_layout_color_t *src); + +video_layout_bounds_t make_bounds (void); +video_layout_bounds_t make_bounds_unit (void); +video_layout_bounds_t bounds_union (const video_layout_bounds_t *a, const video_layout_bounds_t *b); +void bounds_scale (video_layout_bounds_t *dst, const video_layout_bounds_t *dim); +bool bounds_valid (const video_layout_bounds_t *bounds); + +#endif diff --git a/gfx/video_layout/load.c b/gfx/video_layout/load.c new file mode 100644 index 0000000000..3d569596da --- /dev/null +++ b/gfx/video_layout/load.c @@ -0,0 +1,762 @@ +#include "internal.h" +#include "view.h" +#include "scope.h" +#include +#include + +#include +#include +#include + +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: 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 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 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: 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: 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: 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: 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; +} diff --git a/gfx/video_layout/scope.c b/gfx/video_layout/scope.c new file mode 100644 index 0000000000..06d7a6e9f7 --- /dev/null +++ b/gfx/video_layout/scope.c @@ -0,0 +1,339 @@ +#include "scope.h" +#include +#include +#include +#include + +union number +{ + int val_int; + float val_dec; +}; + +typedef struct generator +{ + bool is_decimal; + union number value; + union number increment; + int shift; +} +generator_t; + +struct param +{ + char *name; + char *value; + generator_t *generator; + param_t *prev; + int level; +}; + +static void param_deinit(param_t *param) +{ + free(param->generator); + free(param->value); + free(param->name); +} + +static param_t *param_find(scope_t *scope, const char *name, int level) +{ + param_t *param; + param = scope->param; + + while (param && param->level >= level) + { + if (strcmp(param->name, name) == 0) + { + return param; + } + param = param->prev; + } + + return NULL; +} + +void scope_init(scope_t *scope) +{ + scope->level = 0; + + scope->param = NULL; + + scope->elements = NULL; + scope->elements_count = 0; + + scope->groups = NULL; + scope->groups_count = 0; +} + +void scope_deinit(scope_t *scope) +{ + int i; + param_t *param; + param_t *prev; + + for (i = 0; i < scope->elements_count; ++i) + element_deinit(&scope->elements[i]); + free(scope->elements); + + for (i = 0; i < scope->groups_count; ++i) + view_deinit(&scope->groups[i]); + free(scope->groups); + + for (param = scope->param; param; param = prev) + { + prev = param->prev; + param_deinit(param); + free(param); + } +} + +void scope_push(scope_t *scope) +{ + ++scope->level; +} + +void scope_pop(scope_t *scope) +{ + param_t *param; + + --scope->level; + + while ((param = scope->param)) + { + if (param->level <= scope->level) + break; + + scope->param = param->prev; + param_deinit(param); + free(param); + } +} + +void scope_repeat(scope_t *scope) +{ + param_t *param; + + for (param = scope->param; param && param->level >= scope->level; param = param->prev) + { + generator_t *gen; + if ((gen = param->generator)) + { + char tmp[SCOPE_BUFFER_SIZE]; + tmp[0] = '\0'; + + if (gen->is_decimal) + { + gen->value.val_dec += gen->increment.val_dec; + if (gen->shift > 0) + gen->value.val_dec = (float)((int)gen->value.val_dec << gen->shift); + else if (gen->shift < 0) + gen->value.val_dec = (float)((int)gen->value.val_dec >> -gen->shift); + sprintf(tmp, "%f", gen->value.val_dec); + } + else + { + gen->value.val_int += gen->increment.val_int; + if(gen->shift > 0) + gen->value.val_int <<= gen->shift; + else if (gen->shift < 0) + gen->value.val_int >>= -gen->shift; + sprintf(tmp, "%d", gen->value.val_int); + } + + set_string(¶m->value, tmp); + } + } +} + + +void scope_param(scope_t *scope, const char *name, const char *value) +{ + char *eval_name; + char *eval_value; + param_t *param; + + eval_name = init_string(scope_eval(scope, name)); + eval_value = init_string(scope_eval(scope, value)); + + if ((param = param_find(scope, eval_name, scope->level))) + { + free(param->value); + param->value = eval_value; + } + else + { + param = (param_t*)malloc(sizeof(param_t)); + param->name = init_string(name); + param->value = eval_value; + param->generator = NULL; + param->level = scope->level; + param->prev = scope->param; + scope->param = param; + } + + free(eval_name); +} + +void scope_generator(scope_t *scope, const char *name, const char *start, const char *increment, const char *lshift, const char *rshift) +{ + char *e_name; + char *e_val; + char *e_inc; + generator_t *gen; + param_t *param; + + e_name = init_string(scope_eval(scope, name)); + + if (param_find(scope, e_name, scope->level)) + { + free(e_name); + return; + } + + e_val = init_string(scope_eval(scope, start)); + e_inc = init_string(scope_eval(scope, increment)); + + gen = (generator_t*)malloc(sizeof(generator_t)); + + param = (param_t*)malloc(sizeof(param_t)); + param->name = init_string(e_name); + param->value = init_string(e_val); + param->generator = gen; + param->level = scope->level; + param->prev = scope->param; + scope->param = param; + + gen->is_decimal = is_decimal(e_val) | is_decimal(e_inc); + + if (gen->is_decimal) + { + gen->value.val_dec = get_dec(e_val); + gen->increment.val_dec = get_dec(e_inc); + } + else + { + gen->value.val_int = get_int(e_val); + gen->increment.val_int = get_int(e_inc); + } + + gen->shift = 0; + + if (lshift) + gen->shift += get_int(scope_eval(scope, lshift)); + + if (rshift) + gen->shift -= get_int(scope_eval(scope, rshift)); + + free(e_inc); + free(e_val); + free(e_name); +} + +const char *scope_eval(scope_t *scope, const char *src) +{ + const char* next; + bool in_var; + + char tmp[SCOPE_BUFFER_SIZE]; + + if (!src) + return NULL; + + scope->eval[0] = '\0'; + next = src; + + while (next[0] != '\0') + { + const char* cur; + cur = next; + + if ((in_var = (next[0] == '~'))) + ++cur; + + next = strchr(cur, '~'); + + if (next && next != cur) + { + size_t len; + len = next - cur; + + if (in_var) + { + param_t *param; + + strncpy(tmp, cur, len); + tmp[len] = '\0'; + + if ((param = param_find(scope, tmp, 0))) + strcat(scope->eval, param->value); + else + strcat(scope->eval, tmp); + + ++next; + } + else + { + strncat(scope->eval, cur, len); + } + } + else + { + if (in_var) + --cur; + strcat(scope->eval, cur); + break; + } + } + + return scope->eval; +} + +element_t *scope_add_element(scope_t *scope) +{ + element_t *elem; + + vec_size((void**)&scope->elements, sizeof(element_t), ++scope->elements_count); + + elem = &scope->elements[scope->elements_count - 1]; + element_init(elem, NULL, 0); + + return elem; +} + +element_t *scope_find_element(scope_t *scope, const char *name) +{ + int i; + + for (i = 0; i < scope->elements_count; ++i) + { + if (strcmp(name, scope->elements[i].name) == 0) + return &scope->elements[i]; + } + + return NULL; +} + +view_t *scope_add_group(scope_t *scope) +{ + view_t *group; + + vec_size((void**)&scope->groups, sizeof(view_t), ++scope->groups_count); + + group = &scope->groups[scope->groups_count - 1]; + view_init(group, NULL); + + return group; +} + +view_t *scope_find_group(scope_t *scope, const char *name) +{ + int i; + + for (i = 0; i < scope->groups_count; ++i) + { + if (strcmp(name, scope->groups[i].name) == 0) + return &scope->groups[i]; + } + + return NULL; +} diff --git a/gfx/video_layout/scope.h b/gfx/video_layout/scope.h new file mode 100644 index 0000000000..5c62f27fd3 --- /dev/null +++ b/gfx/video_layout/scope.h @@ -0,0 +1,42 @@ +#ifndef VIDEO_LAYOUT_SCOPE_H +#define VIDEO_LAYOUT_SCOPE_H +#include "view.h" +#include "element.h" + +#define SCOPE_BUFFER_SIZE 256 + +typedef struct param param_t; + +typedef struct scope +{ + int level; + + param_t *param; + + element_t *elements; + int elements_count; + + view_t *groups; + int groups_count; + + char eval[SCOPE_BUFFER_SIZE]; +} +scope_t; + +void scope_init (scope_t *scope); +void scope_deinit (scope_t *scope); +void scope_push (scope_t *scope); +void scope_pop (scope_t *scope); +void scope_repeat (scope_t *scope); + +void scope_param (scope_t *scope, const char *name, const char *value); +void scope_generator (scope_t *scope, const char *name, const char *start, const char *increment, const char *lshift, const char *rshift); +const char *scope_eval (scope_t *scope, const char *src); + +element_t *scope_add_element (scope_t *scope); +element_t *scope_find_element (scope_t *scope, const char *name); + +view_t *scope_add_group (scope_t *scope); +view_t *scope_find_group (scope_t *scope, const char *name); + +#endif diff --git a/gfx/video_layout/types.h b/gfx/video_layout/types.h new file mode 100644 index 0000000000..b5af0729e3 --- /dev/null +++ b/gfx/video_layout/types.h @@ -0,0 +1,49 @@ +#ifndef VIDEO_LAYOUT_TYPES_H +#define VIDEO_LAYOUT_TYPES_H + +typedef unsigned char video_layout_orientation_t; + +#define VIDEO_LAYOUT_FLIP_X 1 +#define VIDEO_LAYOUT_FLIP_Y 2 +#define VIDEO_LAYOUT_SWAP_XY 4 + +#define VIDEO_LAYOUT_ROT0 0 +#define VIDEO_LAYOUT_ROT90 VIDEO_LAYOUT_SWAP_XY | VIDEO_LAYOUT_FLIP_X +#define VIDEO_LAYOUT_ROT180 VIDEO_LAYOUT_FLIP_X | VIDEO_LAYOUT_FLIP_Y +#define VIDEO_LAYOUT_ROT270 VIDEO_LAYOUT_SWAP_XY | VIDEO_LAYOUT_FLIP_Y + +typedef enum video_layout_blend +{ + VIDEO_LAYOUT_BLEND_ALPHA, + VIDEO_LAYOUT_BLEND_ADD, + VIDEO_LAYOUT_BLEND_MOD +} +video_layout_blend_t; + +typedef enum video_layout_text_align +{ + VIDEO_LAYOUT_TEXT_ALIGN_CENTER, + VIDEO_LAYOUT_TEXT_ALIGN_LEFT, + VIDEO_LAYOUT_TEXT_ALIGN_RIGHT +} +video_layout_text_align_t; + +typedef struct video_layout_color +{ + float r; + float g; + float b; + float a; +} +video_layout_color_t; + +typedef struct video_layout_bounds +{ + float x; + float y; + float w; + float h; +} +video_layout_bounds_t; + +#endif diff --git a/gfx/video_layout/view.c b/gfx/video_layout/view.c new file mode 100644 index 0000000000..64242d6da5 --- /dev/null +++ b/gfx/video_layout/view.c @@ -0,0 +1,259 @@ +#include "view.h" +#include +#include + +void layer_init(layer_t *layer, const char *name) +{ + layer->name = init_string(name); + layer->blend = VIDEO_LAYOUT_BLEND_ALPHA; + layer->elements = NULL; + layer->elements_count = 0; +} + +void layer_deinit(layer_t *layer) +{ + int i; + + for (i = 0; i < layer->elements_count; ++i) + element_deinit(&layer->elements[i]); + + free(layer->elements); + free(layer->name); +} + +element_t *layer_add_element(layer_t *layer) +{ + element_t *elem; + + vec_size((void**)&layer->elements, sizeof(element_t), ++layer->elements_count); + + elem = &layer->elements[layer->elements_count - 1]; + element_init(elem, NULL, 0); + + return elem; +} + +void view_init(view_t *view, const char *name) +{ + view->name = init_string(name); + view->bounds = make_bounds(); + view->render_bounds = make_bounds_unit(); + view->layers = NULL; + view->layers_count = 0; + view->screens = NULL; + view->screens_count = 0; +} + +void view_deinit(view_t *view) +{ + int i; + + free(view->screens); + + for (i = 0; i < view->layers_count; ++i) + layer_deinit(&view->layers[i]); + + free(view->layers); + free(view->name); +} + +layer_t *view_find_layer(view_t *view, const char *name) +{ + int i; + + for (i = 0; i < view->layers_count; ++i) + { + if (strcmp(name, view->layers[i].name) == 0) + return &view->layers[i]; + } + + return NULL; +} + +layer_t *view_emplace_layer(view_t *view, const char *name) +{ + layer_t *layer = view_find_layer(view, name); + + if (!layer) + { + vec_size((void**)&view->layers, sizeof(layer_t), ++view->layers_count); + + layer = &view->layers[view->layers_count - 1]; + layer_init(layer, name); + } + + return layer; +} + +void view_sort_layers(view_t *view) +{ + layer_t sorted[6]; + layer_t *layer; + int i = 0; + + /* retroarch frame *= screen's color */ + if ((layer = view_find_layer(view, "screen"))) + { + layer->blend = VIDEO_LAYOUT_BLEND_MOD; + sorted[i] = *layer; + ++i; + } + + if ((layer = view_find_layer(view, "overlay"))) + { + layer->blend = VIDEO_LAYOUT_BLEND_MOD; + sorted[i] = *layer; + ++i; + } + + if ((layer = view_find_layer(view, "backdrop"))) + { + layer->blend = VIDEO_LAYOUT_BLEND_ADD; + sorted[i] = *layer; + ++i; + } + + if ((layer = view_find_layer(view, "bezel"))) + { + layer->blend = VIDEO_LAYOUT_BLEND_ALPHA; + sorted[i] = *layer; + ++i; + } + + if ((layer = view_find_layer(view, "cpanel"))) + { + layer->blend = VIDEO_LAYOUT_BLEND_ALPHA; + sorted[i] = *layer; + ++i; + } + + if ((layer = view_find_layer(view, "marquee"))) + { + layer->blend = VIDEO_LAYOUT_BLEND_ALPHA; + sorted[i] = *layer; + ++i; + } + + for (i = 0; i < view->layers_count; ++i) + view->layers[i] = sorted[i]; +} + +void view_normalize(view_t *view) +{ + video_layout_bounds_t dim; + int i, j; + + if (bounds_valid(&view->bounds)) + { + dim.x = view->bounds.x / view->bounds.w; + dim.y = view->bounds.y / view->bounds.h; + dim.w = 1.0f / view->bounds.w; + dim.h = 1.0f / view->bounds.h; + + if (view->bounds.w < view->bounds.h) + { + view->bounds.w = view->bounds.w / view->bounds.h; + view->bounds.h = 1.f; + } + else + { + view->bounds.h = view->bounds.h / view->bounds.w; + view->bounds.w = 1.f; + } + + view->bounds.x = 0; + view->bounds.y = 0; + } + else + { + dim = view->bounds = make_bounds_unit(); + } + + for (i = 0; i < view->layers_count; ++i) + { + layer_t *layer; + layer = &view->layers[i]; + + for (j = 0; j < layer->elements_count; ++j) + { + element_t *elem; + elem = &layer->elements[j]; + + if (bounds_valid(&elem->bounds)) + { + bounds_scale(&elem->bounds, &dim); + } + else + { + elem->bounds = dim; + } + + elem->bounds.x -= dim.x; + elem->bounds.y -= dim.y; + } + } +} + +void view_count_screens(view_t *view) +{ + int idx, i, j, k; + + idx = -1; + + for (i = 0; i < view->layers_count; ++i) + { + layer_t *layer = &view->layers[i]; + for (j = 0; j < layer->elements_count; ++j) + { + element_t *elem = &layer->elements[j]; + for (k = 0; k < elem->components_count; ++k) + { + component_t *comp = &elem->components[k]; + if (comp->type == VIDEO_LAYOUT_C_SCREEN) + idx = MAX(idx, comp->attr.screen.index); + } + } + } + + if (view->screens_count) + { + free(view->screens); + view->screens_count = 0; + } + + if ((++idx)) + { + view->screens = (video_layout_bounds_t*)calloc(idx, sizeof(video_layout_bounds_t)); + view->screens_count = idx; + } +} + +void view_array_init(view_array_t *view_array, int views_count) +{ + view_array->views = (view_t*)(views_count > 0 ? + calloc(views_count, sizeof(view_t)) : NULL); + view_array->views_count = views_count; +} + +void view_array_deinit(view_array_t *view_array) +{ + int i; + + for (i = 0; i < view_array->views_count; ++i) + view_deinit(&view_array->views[i]); + free(view_array->views); + view_array->views = NULL; + view_array->views_count = 0; +} + +view_t *view_array_find(view_array_t *view_array, const char *name) +{ + int i; + + for (i = 0; i < view_array->views_count; ++i) + { + if (strcmp(name, view_array->views[i].name) == 0) + return &view_array->views[i]; + } + return NULL; +} diff --git a/gfx/video_layout/view.h b/gfx/video_layout/view.h new file mode 100644 index 0000000000..2d772cbfa9 --- /dev/null +++ b/gfx/video_layout/view.h @@ -0,0 +1,53 @@ +#ifndef VIDEO_LAYOUT_VIEW_H +#define VIDEO_LAYOUT_VIEW_H +#include "internal.h" +#include "element.h" + +typedef struct layer +{ + char *name; + video_layout_blend_t blend; + + element_t *elements; + int elements_count; +} +layer_t; + +typedef struct view +{ + char *name; + video_layout_bounds_t bounds; + video_layout_bounds_t render_bounds; + + layer_t *layers; + int layers_count; + + video_layout_bounds_t *screens; + int screens_count; +} +view_t; + +typedef struct view_array +{ + view_t *views; + int views_count; +} +view_array_t; + +void layer_init (layer_t *layer, const char *name); +void layer_deinit (layer_t *layer); +element_t *layer_add_element (layer_t *layer); + +void view_init (view_t *view, const char *name); +void view_deinit (view_t *view); +layer_t *view_find_layer (view_t *view, const char *name); +layer_t *view_emplace_layer (view_t *view, const char *name); +void view_sort_layers (view_t *view); +void view_normalize (view_t *view); +void view_count_screens (view_t *view); + +void view_array_init (view_array_t *view_array, int views_count); +void view_array_deinit (view_array_t *view_array); +view_t *view_array_find (view_array_t *view_array, const char *name); + +#endif