#ifndef INTERACT_H #define INTERACT_H #include "api.h" #include "types.h" static void selected_aabb(userdata_t *ud, float *min_x, float *min_y, float *max_x, float *max_y) { bool first = true; for (int i = 0; i < ud->shapes.count; i++) { shape_t *s = (shape_t*) vec_get(&ud->shapes, i); if (!s->selected) continue; float sc = cosf(s->rotation), ss = sinf(s->rotation); for (uint32_t v = 0; v < s->num_verts; v++) { float lx = s->verts[v].x * s->sx; float ly = s->verts[v].y * s->sy; float wx = s->cx + lx * sc - ly * ss; float wy = s->cy + lx * ss + ly * sc; if (first) { *min_x = *max_x = wx; *min_y = *max_y = wy; first = false; } else { if (wx < *min_x) *min_x = wx; if (wx > *max_x) *max_x = wx; if (wy < *min_y) *min_y = wy; if (wy > *max_y) *max_y = wy; } } } } static void update_shape_states(userdata_t *ud) { for (int i = 0; i < ud->shapes.count; i++) { shape_t *s = (shape_t*) vec_get(&ud->shapes, i); shape_set_state(s, s->hovered, s->selected); } } static void select_group_recursive(userdata_t *ud, int gid) { for (int i = 0; i < ud->shapes.count; i++) { shape_t *s = (shape_t*) vec_get(&ud->shapes, i); if (is_shape_in_group_hierarchy(s->group_id, gid, &ud->groups)) { s->selected = true; ud->interact.selected_count++; } } } static void deselect_and_select_group_recursive(userdata_t *ud, int gid) { for (int i = 0; i < ud->shapes.count; i++) ((shape_t*) vec_get(&ud->shapes, i))->selected = false; ud->interact.selected_count = 0; select_group_recursive(ud, gid); } static void toggle_group_recursive(userdata_t *ud, int gid) { int total = 0, sel = 0; for (int i = 0; i < ud->shapes.count; i++) { shape_t *s = (shape_t*) vec_get(&ud->shapes, i); if (is_shape_in_group_hierarchy(s->group_id, gid, &ud->groups)) { total++; if (s->selected) sel++; } } bool all_sel = (sel == total && total > 0); for (int i = 0; i < ud->shapes.count; i++) { shape_t *s = (shape_t*) vec_get(&ud->shapes, i); if (is_shape_in_group_hierarchy(s->group_id, gid, &ud->groups)) { s->selected = !all_sel; ud->interact.selected_count += s->selected ? 1 : -1; } } } static void rebuild_groups_from_shapes(vector_t *groups, vector_t *shapes) { // Save existing parent relationships so nested groups survive rebuild int old_count = groups->count; int *saved = NULL; if (old_count > 0) { saved = (int*) ALLOC((size_t)old_count * 2 * sizeof(int)); for (int i = 0; i < old_count; i++) { group_t *g = (group_t*) vec_get(groups, i); saved[i * 2] = g->id; saved[i * 2 + 1] = g->parent_id; } } groups->count = 0; for (int i = 0; i < shapes->count; i++) { shape_t *s = (shape_t*) vec_get(shapes, i); if (s->group_id == 0) continue; bool found = false; for (int g = 0; g < groups->count; g++) { if (((group_t*) vec_get(groups, g))->id == s->group_id) { found = true; break; } } if (!found) { group_t grp = { .id = s->group_id, .parent_id = 0 }; *((group_t*) vec_push(groups)) = grp; } } // Restore parent relationships for groups that still exist for (int i = 0; i < old_count; i++) { int gid = saved[i * 2]; int pid = saved[i * 2 + 1]; if (pid == 0) continue; group_t *g = find_group(groups, gid); if (g) { // Only restore if parent group still exists if (find_group(groups, pid)) g->parent_id = pid; } } if (saved) FREE(saved); } static int hit_test_resize_handles(userdata_t *ud, float wx, float wy, float tol) { if (ud->interact.selected_count <= 0) return -1; float omin[2], omax[2]; if (ud->interact.aabb_cached) { omin[0] = ud->interact.cached_aabb[0]; omin[1] = ud->interact.cached_aabb[1]; omax[0] = ud->interact.cached_aabb[2]; omax[1] = ud->interact.cached_aabb[3]; } else { selected_aabb(ud, &omin[0], &omin[1], &omax[0], &omax[1]); } float hs = CORNER_SIZE_PX / ud->camera.zoom * 0.5f + tol; float mid_x = (omin[0] + omax[0]) * 0.5f; float mid_y = (omin[1] + omax[1]) * 0.5f; float hx[8] = {omin[0], mid_x, omax[0], omax[0], omax[0], mid_x, omin[0], omin[0]}; float hy[8] = {omin[1], omin[1], omin[1], mid_y, omax[1], omax[1], omax[1], mid_y}; for (int h = 0; h < 8; h++) { if (fabsf(wx - hx[h]) <= hs && fabsf(wy - hy[h]) <= hs) return h; } return -1; } #endif