dorne/cursetree/node.c
Emile Clark-Boman 6b5bcff1a4 (UNDER CONSTRUCTION) implementing general child nodes
bifurcation was limited and I needed something more powerful
2025-09-15 17:11:06 +10:00

351 lines
10 KiB
C

#include <assert.h>
#include <math.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "dims.h"
#include "node.h"
#include "surface.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 *new_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){
/* 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
*/
.surface = new_surface(parent->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;
}
/* 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);
}
void satisfy_node(struct ct_node *const node, struct ct_dims *const dims) {
double axismax;
if (IS_PARENT_NODE(node)) {
for (int i = 0; i < node->cindex; i++) {
if (node->axis == AXIS_X) {
axismax += dims->width * node->child[i]->surface->bounds->wmax;
} else {
assert(node->axis == AXIS_Y);
axismax += dims->width * node->child[i]->surface->bounds->hmax;
}
}
}
}
struct ct_whdims {
// Main Axis & Orthogonal Axis Sizes
int axm_size, axo_size;
bool fixed;
int min, max;
};
/*
*/
int resize_node(struct ct_node *const node, struct ct_dims *const dims) {
// if (IS_PARENT_NODE(node)) {
// dims->width / node->cindex;
// }
resize_surface(node->surface, dims);
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_TOOSMALL;
return 1;
}
struct ct_whdims cwhdims[node->cindex];
struct ct_whdims *const parts[node->cindex];
int cunfixedn = node->cindex; // number of non-fixed children
memset(cwhdims, 0, sizeof(struct ct_whdims) * node->cindex);
int axm_size, axm_free, axo_size;
size_t axm_min_offset, axm_max_offset;
if (node->axis == AXIS_X) {
axm_free = dims->width;
axm_size = dims->width;
axo_size = dims->height;
axm_min_offset = offsetof(struct ct_bounds, wmin);
axm_max_offset = offsetof(struct ct_bounds, wmax);
} else {
assert(node->axis == AXIS_Y);
axm_free = dims->height;
axm_size = dims->height;
axo_size = dims->width;
axm_min_offset = offsetof(struct ct_bounds, hmin);
axm_max_offset = offsetof(struct ct_bounds, hmax);
}
int split = axm_free / node->cindex;
for (int j = 0; j < node->cindex; j++) {
cwhdims[j].min = *(int*)(node->child[j]->surface->bounds + axm_min_offset);
cwhdims[j].max = *(int*)(node->child[j]->surface->bounds + axm_max_offset);
if (node->child[j]->surface->bounds->type == BOUND_RELATIVE) {
cwhdims[j].min *= axm_size;
cwhdims[j].max *= axm_size;
}
cwhdims[j].axm_size = fmax(fmin(split, cwhdims[j].max), cwhdims[j].min);
cwhdims[j].axo_size = axo_size;
axm_free -= cwhdims[j].axm_size;
}
int delta;
while (axm_free) {
if (axm_free < cunfixedn) {
int progress = 0;
for (int j = 0; j < node->cindex; j++) {
if (cwhdims[j].fixed)
continue;
cwhdims[j].axm_size--;
axm_free--;
}
}
// split is guaranteed to be >0
split = axm_free / node->cindex;
for (int j = 0; j < node->cindex; j++) {
if (cwhdims[j].fixed)
continue;
delta = cwhdims[j].axm_size - fmax(cwhdims[j].axm_size - split, bounds->wmin);
if (delta == 0) {
cwhdims[j].fixed = true;
cunfixedn++;
continue;
}
cwhdims[j].axm_size -= delta;
axm_free -= delta;
}
}
// bifurcate_dims(dims, node->axis, node->ratio, &dims0, &dims1);
// resize_node(node->child[0], dims0);
// resize_node(node->child[1], dims1);
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
parent->csize *= NODE_CHILDREN_GROWTH;
if (parent->csize > CINDEX_MAX)
parent->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);
}