#include #include #include #include #include #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); }