359 lines
11 KiB
C
359 lines
11 KiB
C
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "dims.h"
|
|
#include "node.h"
|
|
#include "surface.h"
|
|
#include "util.h"
|
|
|
|
/* Internal allocator method for ct_node structures.
|
|
*/
|
|
static inline struct ct_node *__alloc_node(void) {
|
|
struct ct_node *node = (struct ct_node *)malloc(sizeof(struct ct_node));
|
|
return node;
|
|
}
|
|
|
|
/* Returns NULL if memory allocation failed.
|
|
* TODO: should dims be given as a parameter, or lazily computed in ct_update?
|
|
*/
|
|
struct ct_node *__node(struct ct_dims *const dims,
|
|
struct ct_bounds *const bounds,
|
|
struct ct_node *const parent) {
|
|
struct ct_node *node = __alloc_node();
|
|
if (node != NULL) {
|
|
*node = (struct ct_node){
|
|
.surface = new_surface(dims, bounds),
|
|
.flags = NFLAG_EMPTY,
|
|
|
|
.parent = parent,
|
|
.child = (struct ct_node **)malloc(NODE_INIT_CHILDREN *
|
|
sizeof(struct ct_node *)),
|
|
.csize = NODE_INIT_CHILDREN,
|
|
.cindex = 0,
|
|
.axis = AXIS_X,
|
|
};
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
struct ct_node *new_node(struct ct_bounds *const bounds,
|
|
struct ct_node *const parent) {
|
|
/* copy the parent's dimensions for now and request
|
|
* cursetree resize this node appropriately afterwards
|
|
* WARNING: new_node doesn't set the NFLAG_RESIZE request flag
|
|
* WARNING: that should be done by a function calling new_node
|
|
*/
|
|
return __node(dup_dims(parent->surface->dims), bounds, parent);
|
|
}
|
|
|
|
/* WARNING: Do NOT use __destroy_node() to destroy a node's children!
|
|
* WARNING: Use the destroy_child_node() function instead.
|
|
*/
|
|
void __destroy_node(struct ct_node *const node) {
|
|
if (node == NULL)
|
|
return;
|
|
|
|
// destroy children first
|
|
for (int j = 0; j < node->cindex; j++) {
|
|
__destroy_node(node->child[j]);
|
|
}
|
|
|
|
destroy_surface(node->surface);
|
|
free(node);
|
|
}
|
|
|
|
/* Surface Partition Dimensions */
|
|
/* TODO: can I use unsigned short instead of int? */
|
|
struct ct_spdims {
|
|
// Main Axis & Orthogonal Axis Sizes
|
|
int axm_size;
|
|
bool fixed;
|
|
int min, max;
|
|
};
|
|
|
|
/*
|
|
*/
|
|
int resize_node(struct ct_node *const node, struct ct_dims *dims) {
|
|
resize_surface(node->surface, dims);
|
|
dims = node->surface->dims;
|
|
|
|
if (node->surface->bounds->wmin > dims->width ||
|
|
node->surface->bounds->hmin > dims->height) {
|
|
node->flags |= NFLAG_SMALL;
|
|
return 1;
|
|
} else if (node->surface->bounds->wmax < dims->width ||
|
|
node->surface->bounds->hmax < dims->height) {
|
|
node->flags |= NFLAG_LARGE;
|
|
return 1;
|
|
}
|
|
|
|
if (!IS_PARENT_NODE(node))
|
|
return 0;
|
|
|
|
if (node->cbounds.wmin_abs + node->cbounds.wmin_rel * dims->width >
|
|
dims->width ||
|
|
node->cbounds.hmin_abs + node->cbounds.hmin_rel * dims->height >
|
|
dims->height) {
|
|
node->flags |= NFLAG_SMALLCHILD;
|
|
return 1;
|
|
}
|
|
|
|
int axm_size, axm_free;
|
|
size_t bounds_axm_min_offset, bounds_axm_max_offset;
|
|
size_t dims_axm_pos_offset, dims_axm_size_offset;
|
|
|
|
if (node->axis == AXIS_X) {
|
|
axm_free = dims->width;
|
|
axm_size = dims->width;
|
|
|
|
dims_axm_pos_offset = offsetof(struct ct_dims, x);
|
|
dims_axm_size_offset = offsetof(struct ct_dims, width);
|
|
bounds_axm_min_offset = offsetof(struct ct_bounds, wmin);
|
|
bounds_axm_max_offset = offsetof(struct ct_bounds, wmax);
|
|
} else {
|
|
assert(node->axis == AXIS_Y);
|
|
axm_free = dims->height;
|
|
axm_size = dims->height;
|
|
|
|
dims_axm_pos_offset = offsetof(struct ct_dims, y);
|
|
dims_axm_size_offset = offsetof(struct ct_dims, height);
|
|
bounds_axm_min_offset = offsetof(struct ct_bounds, hmin);
|
|
bounds_axm_max_offset = offsetof(struct ct_bounds, hmax);
|
|
}
|
|
|
|
struct ct_spdims cspdims[node->cindex];
|
|
cindex parts_n = node->cindex;
|
|
memset(cspdims, 0, sizeof(struct ct_spdims) * node->cindex);
|
|
for (int i = 0; i < node->cindex; i++) {
|
|
cspdims[i].min = *(int *)((char *)node->child[i]->surface->bounds +
|
|
bounds_axm_min_offset);
|
|
cspdims[i].max = *(int *)((char *)node->child[i]->surface->bounds +
|
|
bounds_axm_max_offset);
|
|
|
|
if (node->child[i]->surface->bounds->type == BOUND_RELATIVE) {
|
|
cspdims[i].min *= axm_size;
|
|
cspdims[i].max *= axm_size;
|
|
}
|
|
|
|
cspdims[i].axm_size = 0;
|
|
}
|
|
|
|
int split, new_size;
|
|
while (axm_free && parts_n) {
|
|
split = (axm_free > parts_n) ? (axm_free / parts_n) : 1;
|
|
for (int i = 0; i < node->cindex; i++) {
|
|
if (cspdims[i].fixed)
|
|
continue;
|
|
|
|
new_size =
|
|
clampi(cspdims[i].axm_size + split, cspdims[i].min, cspdims[i].max);
|
|
|
|
if (new_size == cspdims[i].axm_size) {
|
|
cspdims[i].fixed = true;
|
|
parts_n--;
|
|
continue;
|
|
}
|
|
|
|
axm_free -= (new_size - cspdims[i].axm_size);
|
|
cspdims[i].axm_size = new_size;
|
|
|
|
if (axm_free == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct ct_dims *cdims = dup_dims(dims);
|
|
/* NOTE: the statements below are done implicitly by dup_dims */
|
|
// *(int*)(cdims + dims_axm_pos_offset) = *(int*)(dims +
|
|
// dims_axm_pos_offset);
|
|
// *(int*)(cdims + dims_axo_pos_offset) = *(int*)(dims +
|
|
// dims_axo_pos_offset);
|
|
// *(int*)(cdims + dims_axo_size_offset) = axo_size;
|
|
for (int i = 0; i < node->cindex; i++) {
|
|
*(int *)((char *)cdims + dims_axm_size_offset) = cspdims[i].axm_size;
|
|
resize_node(node->child[i], dup_dims(cdims));
|
|
*(int *)((char *)cdims + dims_axm_pos_offset) += cspdims[i].axm_size;
|
|
}
|
|
free(cdims);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __set_cbounds(struct ct_node *const parent,
|
|
const struct ct_node *const child,
|
|
const bool additive) {
|
|
/* child and parent w/h min aliases */
|
|
int c_wmin, c_hmin;
|
|
int p_wmin_rel, p_hmin_rel;
|
|
|
|
c_wmin = child->surface->bounds->wmin;
|
|
c_hmin = child->surface->bounds->hmin;
|
|
|
|
if (!additive) {
|
|
c_wmin *= -1;
|
|
c_hmin *= -1;
|
|
}
|
|
|
|
if (child->surface->bounds->type == BOUND_RELATIVE) {
|
|
parent->cbounds.wmin_abs++;
|
|
parent->cbounds.hmin_abs++;
|
|
|
|
p_wmin_rel = parent->cbounds.wmin_rel + c_wmin;
|
|
p_hmin_rel = parent->cbounds.hmin_rel + c_wmin;
|
|
if (p_wmin_rel > __BOUND_REL_MAX || p_hmin_rel >= __BOUND_REL_MAX)
|
|
return 1;
|
|
|
|
parent->cbounds.wmin_rel = p_wmin_rel;
|
|
parent->cbounds.hmin_rel = p_hmin_rel;
|
|
assert(parent->cbounds.wmin_rel >= __BOUND_REL_MIN);
|
|
assert(parent->cbounds.hmin_rel >= __BOUND_REL_MIN);
|
|
} else {
|
|
parent->cbounds.wmin_abs += c_wmin;
|
|
parent->cbounds.hmin_abs += c_hmin;
|
|
assert(parent->cbounds.wmin_abs >= __BOUND_ABS_MIN);
|
|
assert(parent->cbounds.hmin_abs >= __BOUND_ABS_MIN);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Returns:
|
|
* 0 -> success
|
|
* 1 -> failed (max child limit reached)
|
|
* 2 -> failed (cumulative relative bounds surpasses 100%)
|
|
*/
|
|
int insert_child_node(struct ct_node *const parent, struct ct_node *const child,
|
|
const cindex i) {
|
|
if (parent->cindex == CINDEX_MAX)
|
|
return 1;
|
|
else if (__set_cbounds(parent, child, true))
|
|
return 2;
|
|
else if (parent->cindex == parent->csize) {
|
|
// grow child array size and clamp maximum
|
|
cindex new_csize = parent->csize * NODE_CHILDREN_GROWTH;
|
|
// check overflow
|
|
parent->csize = (parent->csize <= new_csize) ? new_csize : CINDEX_MAX;
|
|
|
|
parent->child =
|
|
reallocarray(parent->child, parent->csize, sizeof(struct ct_node *));
|
|
}
|
|
|
|
// shift all children up for insertion
|
|
for (int j = parent->cindex; j > i; j--) {
|
|
parent->child[j] = parent->child[j - 1];
|
|
}
|
|
// do insertion
|
|
parent->child[i] = child;
|
|
parent->cindex++;
|
|
// request cursetree recompute dimensions recursively from parent
|
|
parent->flags |= NFLAG_RESIZE;
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
/* Returns:
|
|
* 0 -> success
|
|
* 1 -> failed (max child limit reached)
|
|
* 2 -> failed (cumulative relative bounds surpasses 100%)
|
|
*/
|
|
int append_child_node(struct ct_node *const parent,
|
|
struct ct_node *const child) {
|
|
return insert_child_node(parent, child, parent->cindex);
|
|
}
|
|
|
|
/* Remove a child node from a parent node by index.
|
|
* Returns NULL if an invalid index was provided, otherwise returns
|
|
* a pointer to the child node removed.
|
|
* NOTE: This does NOT destroy the child node and should be
|
|
* NOTE: used when moving the child somewhere else.
|
|
* NOTE: Otherwise use destroy_child_node() instead.
|
|
*/
|
|
struct ct_node *remove_child_node(struct ct_node *const parent,
|
|
const cindex i) {
|
|
struct ct_node *child;
|
|
|
|
if (i >= parent->cindex)
|
|
return NULL;
|
|
else if (parent->cindex <= parent->csize / NODE_CHILDREN_GROWTH) {
|
|
// shrink child array to avoid memory bloat
|
|
parent->csize /= NODE_CHILDREN_GROWTH;
|
|
parent->child =
|
|
reallocarray(parent->child, parent->csize, sizeof(struct ct_node *));
|
|
}
|
|
|
|
child = &parent[i];
|
|
// shift all children down to fill removal
|
|
for (int j = i; j < parent->cindex; j++) {
|
|
parent->child[j] = parent->child[j + 1];
|
|
}
|
|
parent->cindex--;
|
|
__set_cbounds(parent, child, false);
|
|
// request cursetree recompute dimensions recursively from parent
|
|
parent->flags |= NFLAG_RESIZE;
|
|
return child;
|
|
}
|
|
|
|
/* Remove and destroy a child node by index.
|
|
* Returns 1 on failure (invalid index provided), and 0 on success.
|
|
* NOTE: Use remove_child_node() instead if the child should live.
|
|
*/
|
|
int destroy_child_node(struct ct_node *const parent, const cindex i) {
|
|
struct ct_node *child = remove_child_node(parent, i);
|
|
__destroy_node(child);
|
|
return (child == NULL);
|
|
}
|
|
|
|
/*
|
|
* Returns:
|
|
* 0 -> success
|
|
* 1 -> failed (node has no parent)
|
|
* 2 -> failed (parent has no reference to node, CRITICAL)
|
|
*/
|
|
static int __index_as_child(const struct ct_node *const node,
|
|
int *const index) {
|
|
if (node->parent == NULL)
|
|
return 1;
|
|
|
|
for (int i = 0; i < node->parent->cindex; i++) {
|
|
if (node->parent->child[i] == node) {
|
|
*index = i;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
/* If preserve_bounds is set then upon collapse the new node maintains the
|
|
* original node's bounds struct. Otherwise the bounds of the child collapsed
|
|
* onto are used.
|
|
*/
|
|
void collapse_node(struct ct_node **const node, const int i,
|
|
const bool preserve_bounds) {
|
|
assert(0 <= i && i < (*node)->cindex);
|
|
int parent_index;
|
|
struct ct_node *const parent = (*node)->parent;
|
|
struct ct_node *collapse_target = remove_child_node(*node, i);
|
|
struct ct_dims *dims = dup_dims((*node)->surface->dims);
|
|
struct ct_bounds *bounds;
|
|
|
|
if (preserve_bounds) {
|
|
bounds = dup_bounds((*node)->surface->bounds);
|
|
rebind_surface(collapse_target->surface, bounds);
|
|
}
|
|
|
|
/* Destroy original node and point to the collapse_target */
|
|
if (__index_as_child(*node, &parent_index)) {
|
|
/* __index_as_child fails (typically) because
|
|
* the *node is cursetree's root node. */
|
|
__destroy_node(*node);
|
|
*node = collapse_target;
|
|
} else {
|
|
destroy_child_node(parent, parent_index);
|
|
insert_child_node(parent, collapse_target, parent_index);
|
|
}
|
|
|
|
resize_node(collapse_target, dims);
|
|
}
|