/* * Copyright (C) 2018 Vito Caputo - * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3 as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "list.h" #include "stage.h" struct stage_node_t { list_head_t nodes; char name[STAGE_NODE_NAME_MAX]; float alpha; /* alpha for the texture when composited */ unsigned active:1; /* node is active */ unsigned locked:1; /* node is locked */ stage_render_func_t render; /* render object into a texture */ stage_free_func_t free; /* free object */ void *object; /* object */ }; struct stage_t { list_head_t layers[STAGE_LAYERS_MAX]; float alpha; /* alpha to apply to all nodes */ }; /* minimally initialize a node to carry an object */ static void node_init(stage_node_t *node, const char *name, void *object, stage_render_func_t render_func, stage_free_func_t free_func) { strncpy(node->name, name, sizeof(node->name)); node->render = render_func; node->free = free_func; node->object = object; } /* allocate a node, no list manipulation occurs, this purely creates a node in isolation and assigns its associated name, object and functions */ static stage_node_t * node_new(const char *name, void *object, stage_render_func_t render_func, stage_free_func_t free_func) { stage_node_t *node; node = calloc(1, sizeof(stage_node_t)); if (!node) return NULL; node_init(node, name, object, render_func, free_func); return node; } /* free a node, no list manipulation occurs, this is purely cleanup of the node and its associated texture and object */ static void node_free(stage_t *stage, stage_node_t *node) { if (node->free) node->free(stage, node, node->object); free(node); } /* returns a new node installed at the specified layer */ stage_node_t * stage_node_new(stage_t *stage, int layer, const char *name, void *object, stage_render_func_t render_func, stage_free_func_t free_func) { stage_node_t *node; node = node_new(name, object, render_func, free_func); if (!node) return NULL; list_add_tail(&node->nodes, &stage->layers[layer]); return node; } /* replace a given node's object only - retain render/free hooks and everything else, invalidates cache - DOES NOT FREE OLD OBJECT */ void stage_node_set_object(const stage_t *stage, stage_node_t *node, void *object) { node->object = object; } void stage_node_get_object(const stage_t *stage, stage_node_t *node, void **res_object) { *res_object = node->object; } /* replaces a given node's object-related properties but otherwise keeping the existing state */ void stage_node_replace(const stage_t *stage, stage_node_t *node, const char *name, void *object, stage_render_func_t render_func, stage_free_func_t free_func) { if (node->free) node->free(stage, node, node->object); node_init(node, name, object, render_func, free_func); } /* frees a given node removing it from the stage */ stage_node_t * stage_node_free(stage_t *stage, stage_node_t *node) { if (node) { list_del(&node->nodes); node_free(stage, node); } return NULL; } /* set the alpha on a node */ void stage_node_set_alpha(const stage_t *stage, stage_node_t *node, float alpha) { /* TODO: this should probably mark something dirty for when we stop always recomposing everything */ node->alpha = alpha; } /* set a node's active state (participates in rendering) */ void stage_node_set_active(const stage_t *stage, stage_node_t *node, int active) { node->active = active; } /* set a node's locked state (doesn't get freed by clears) */ void stage_node_set_locked(const stage_t *stage, stage_node_t *node, int locked) { node->locked = locked; } /* set a node's layer */ void stage_node_set_layer(stage_t *stage, stage_node_t *node, int layer) { /* TODO: assert layer sanity */ list_del(&node->nodes); list_add_tail(&node->nodes, &stage->layers[layer]); } /* create a new stage with the given aspect ratio */ stage_t * stage_new(void) { stage_t *stage; int i; stage = calloc(1, sizeof(stage_t)); if (!stage) return NULL; for (i = 0; i < STAGE_LAYERS_MAX; i++) INIT_LIST_HEAD(&stage->layers[i]); return stage; } static void _stage_clear(stage_t *stage, int force) { stage_node_t *node, *_node; int i; for (i = 0; i < STAGE_LAYERS_MAX; i++) { list_for_each_entry_safe(node, _node, &stage->layers[i], nodes) { if (force || !node->locked) stage_node_free(stage, node); } } } /* free everything in the stage, but keep the stage around */ /* probably good hygiene to clear everything when there's no intention of * reusing nodes at the start of a new context, in case something is left * around unintentionally. */ void stage_clear(stage_t *stage) { _stage_clear(stage, 0); } /* free the supplied stage and all associated textures and objects */ stage_t * stage_free(stage_t *stage) { if (stage) { _stage_clear(stage, 1); free(stage); } return NULL; } /* fit a stage to the supplied dimensions, returns the fitted dimensions in the result pointers */ /* the idea is this can be used in a window resize hook to enforce the stage's aspect ratio */ void stage_fit(float aspect_ratio, int width, int height, int *res_width, int *res_height) { float full_width = aspect_ratio * ((float)height); if (full_width == width) { /* perfect fit */ *res_width = width; *res_height = height; } else if (full_width > width) { /* height is too large */ *res_height = (1.0f / aspect_ratio) * ((float)width); *res_width = width; } else { /* width is too large */ *res_width = full_width; *res_height = height; } } /* set the global alpha factor for the stage, the individual node alphas are multiplied by this value @ render time. */ void stage_set_alpha(stage_t *stage, float alpha) { stage->alpha = alpha; } /* get the global alpha factor for the stage */ void stage_get_alpha(stage_t *stage, float *res_alpha) { *res_alpha = stage->alpha; } static void render_nodes(const stage_t *stage, const list_head_t *head) { stage_node_t *node; list_for_each_entry(node, head, nodes) { if (!node->active) continue; node->render(stage, node, node->object, stage->alpha * node->alpha); } } /* render the supplied stage into the supplied renderer */ /* the aspect_ratio of the stage will be enforced in filling the logical size * of the renderer. Unless the aspect ratio matches precisely, stage_render() * will center the rendering and leave blank borders on the edges left empty. * The renderer may be sized precisely by first calling stage_fit(). */ void stage_render(const stage_t *stage) { int i; for (i = 0; i < STAGE_LAYERS_MAX; i++) render_nodes(stage, &stage->layers[i]); } /* lookup a node from a name */ stage_node_t * stage_node_lookup_name(const stage_t *stage, const char *name) { stage_node_t *node; int i; for (i = 0; i < STAGE_LAYERS_MAX; i++) { list_for_each_entry(node, &stage->layers[i], nodes) { if (!strncmp(node->name, name, sizeof(node->name))) return node; } } return NULL; }