#ifndef HISTORY_H #define HISTORY_H #include "api.h" typedef enum { HIST_POSITION, HIST_SCALE, HIST_ROTATION, HIST_CREATE, HIST_DELETE, HIST_GROUP, } hist_prop_t; typedef struct hist_change_t { int shape_index; hist_prop_t prop; float old_val[4]; float new_val[4]; // Owned vertex+index buffer snapshot — only used for HIST_CREATE / HIST_DELETE. // Freed when the history entry is discarded or the stack is destroyed. shape_vertex_t *vertex_data; uint16_t *index_data; int vertex_count; int index_count; } hist_change_t; typedef struct hist_entry_t { hist_change_t *changes; int count; } hist_entry_t; typedef struct history_t { vector_t entries; int current; bool capturing; int pending_shape_idx; hist_prop_t pending_prop; float pending_old[4]; } history_t; // -- helpers -- static void hist_read_prop(shape_t *s, hist_prop_t prop, float out[4]) { memset(out, 0, sizeof(float[4])); switch (prop) { case HIST_POSITION: out[0] = s->cx; out[1] = s->cy; break; case HIST_SCALE: out[0] = s->sx; out[1] = s->sy; break; case HIST_ROTATION: out[0] = s->rotation; break; case HIST_GROUP: out[0] = (float)s->group_id; break; default: break; } } static void hist_apply_prop(shape_t *s, hist_prop_t prop, const float val[4]) { switch (prop) { case HIST_POSITION: s->cx = val[0]; s->cy = val[1]; break; case HIST_SCALE: s->sx = val[0]; s->sy = val[1]; break; case HIST_ROTATION: s->rotation = val[0]; break; case HIST_GROUP: s->group_id = (int)val[0]; break; default: break; } } static void hist_free_change(hist_change_t *c) { if (c->vertex_data) FREE(c->vertex_data); if (c->index_data) FREE(c->index_data); memset(c, 0, sizeof(*c)); } static void history_init(history_t *h) { memset(h, 0, sizeof(*h)); vec_init(&h->entries, sizeof(hist_entry_t)); h->current = -1; } static void history_destroy(history_t *h) { for (int i = 0; i < h->entries.count; i++) { hist_entry_t *e = (hist_entry_t*) vec_get(&h->entries, i); if (e->changes) { for (int j = 0; j < e->count; j++) hist_free_change(&e->changes[j]); FREE(e->changes); } } vec_free(&h->entries); memset(h, 0, sizeof(*h)); h->current = -1; } static void history_push_entry(history_t *h, hist_entry_t entry) { while (h->entries.count > h->current + 1) { hist_entry_t *e = (hist_entry_t*) vec_get(&h->entries, h->entries.count - 1); if (e->changes) { for (int j = 0; j < e->count; j++) hist_free_change(&e->changes[j]); FREE(e->changes); } vec_pop(&h->entries); } *((hist_entry_t*) vec_push(&h->entries)) = entry; h->current = h->entries.count - 1; } static void history_begin_edit(history_t *h, vector_t *shapes, int shape_idx, hist_prop_t prop) { if (h->capturing) { shape_t *s = (shape_t*) vec_get(shapes, h->pending_shape_idx); float new_val[4]; hist_read_prop(s, h->pending_prop, new_val); if (memcmp(h->pending_old, new_val, sizeof(float[4])) != 0) { hist_entry_t entry = { .changes = NULL, .count = 1 }; entry.changes = (hist_change_t*) ALLOC(sizeof(hist_change_t)); memset(entry.changes, 0, sizeof(hist_change_t)); entry.changes->shape_index = h->pending_shape_idx; entry.changes->prop = h->pending_prop; memcpy(entry.changes->old_val, h->pending_old, sizeof(float[4])); memcpy(entry.changes->new_val, new_val, sizeof(float[4])); history_push_entry(h, entry); } h->capturing = false; } h->capturing = true; h->pending_shape_idx = shape_idx; h->pending_prop = prop; shape_t *s = (shape_t*) vec_get(shapes, shape_idx); hist_read_prop(s, prop, h->pending_old); } static void history_end_edit(history_t *h, vector_t *shapes) { if (!h->capturing) return; shape_t *s = (shape_t*) vec_get(shapes, h->pending_shape_idx); float new_val[4]; hist_read_prop(s, h->pending_prop, new_val); if (memcmp(h->pending_old, new_val, sizeof(float[4])) != 0) { hist_entry_t entry = { .changes = NULL, .count = 1 }; entry.changes = (hist_change_t*) ALLOC(sizeof(hist_change_t)); memset(entry.changes, 0, sizeof(hist_change_t)); entry.changes->shape_index = h->pending_shape_idx; entry.changes->prop = h->pending_prop; memcpy(entry.changes->old_val, h->pending_old, sizeof(float[4])); memcpy(entry.changes->new_val, new_val, sizeof(float[4])); history_push_entry(h, entry); } h->capturing = false; } // -- batch API -- typedef struct { hist_change_t *changes; int count; int capacity; } hist_batch_t; static void history_batch_init(hist_batch_t *batch, int count) { batch->changes = (hist_change_t*) ALLOC((size_t)count * sizeof(hist_change_t)); memset(batch->changes, 0, (size_t)count * sizeof(hist_change_t)); batch->count = 0; batch->capacity = count; } // For property changes (POSITION, SCALE, ROTATION, GROUP) static void history_batch_add(hist_batch_t *batch, int shape_index, hist_prop_t prop, const float old_val[4], const float new_val[4]) { hist_change_t *c = &batch->changes[batch->count++]; c->shape_index = shape_index; c->prop = prop; memcpy(c->old_val, old_val, sizeof(float[4])); memcpy(c->new_val, new_val, sizeof(float[4])); } // Snapshot a shape's full vertex data and metadata into a change entry. // Used for HIST_CREATE and HIST_DELETE entries. // old_val = { kind, cx, cy, num_verts } // new_val = { sx, sy, rotation, group_id } static void hist_snapshot_shape_verts(hist_change_t *c, shape_t *s) { int n = (int)s->num_elements; c->vertex_count = n; c->index_count = n; c->vertex_data = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); c->index_data = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_t)); memcpy(c->vertex_data, s->verts, (size_t)n * sizeof(shape_vertex_t)); memcpy(c->index_data, s->indices, (size_t)n * sizeof(uint16_t)); c->old_val[0] = (float)s->kind; c->old_val[1] = s->cx; c->old_val[2] = s->cy; c->old_val[3] = (float)s->num_verts; c->new_val[0] = s->sx; c->new_val[1] = s->sy; c->new_val[2] = s->rotation; c->new_val[3] = (float)s->group_id; } // Append a CREATE or DELETE entry to a batch, snapshotting the shape's vertex data. static void history_batch_add_shape(hist_batch_t *batch, int shape_index, hist_prop_t prop, shape_t *s) { hist_change_t *c = &batch->changes[batch->count++]; c->shape_index = shape_index; c->prop = prop; hist_snapshot_shape_verts(c, s); } static void history_batch_commit(hist_batch_t *batch, history_t *h) { hist_entry_t entry = { .changes = batch->changes, .count = batch->count }; history_push_entry(h, entry); } // Reconstruct a shape_t from a HIST_CREATE / HIST_DELETE change snapshot. static shape_t hist_rebuild_shape_from_snapshot(const hist_change_t *c) { shape_t s; memset(&s, 0, sizeof(s)); s.kind = (int)c->old_val[0]; s.cx = c->old_val[1]; s.cy = c->old_val[2]; s.num_verts = (uint32_t)c->old_val[3]; s.num_elements = (uint32_t)c->vertex_count; s.sx = c->new_val[0]; s.sy = c->new_val[1]; s.rotation = c->new_val[2]; s.group_id = (int)c->new_val[3]; int n = c->vertex_count; s.verts = (shape_vertex_t*) ALLOC((size_t)n * sizeof(shape_vertex_t)); s.indices = (uint16_t*) ALLOC((size_t)n * sizeof(uint16_t)); memcpy(s.verts, c->vertex_data, (size_t)n * sizeof(shape_vertex_t)); memcpy(s.indices, c->index_data, (size_t)n * sizeof(uint16_t)); shape_init_common(&s); shape_build_transform(&s); shape_make_buffers(&s); return s; } static void history_apply_entry(hist_entry_t *entry, vector_t *shapes, bool forward) { bool has_shape_ops = false; for (int i = 0; i < entry->count; i++) { hist_prop_t p = entry->changes[i].prop; if (p == HIST_CREATE || p == HIST_DELETE) { has_shape_ops = true; break; } } int start = 0, end = entry->count, step = 1; if (has_shape_ops && !forward) { start = entry->count - 1; end = -1; step = -1; } for (int i = start; i != end; i += step) { hist_change_t *c = &entry->changes[i]; if (c->prop == HIST_CREATE || c->prop == HIST_DELETE) { bool adding = (c->prop == HIST_CREATE) ? forward : !forward; if (adding) { shape_t s = hist_rebuild_shape_from_snapshot(c); *((shape_t*) vec_insert(shapes, c->shape_index)) = s; } else { if (c->shape_index < shapes->count) { shape_t *s = (shape_t*) vec_get(shapes, c->shape_index); shape_shutdown(s); vec_remove_ordered(shapes, c->shape_index); } } continue; } if (c->shape_index >= shapes->count) continue; shape_t *s = (shape_t*) vec_get(shapes, c->shape_index); hist_apply_prop(s, c->prop, forward ? c->new_val : c->old_val); shape_regenerate(s); shape_set_state(s, s->hovered, s->selected); } } static bool history_undo(history_t *h, vector_t *shapes) { if (h->current < 0) return false; history_apply_entry((hist_entry_t*) vec_get(&h->entries, h->current), shapes, false); h->current--; return true; } static bool history_redo(history_t *h, vector_t *shapes) { if (h->current + 1 >= h->entries.count) return false; h->current++; history_apply_entry((hist_entry_t*) vec_get(&h->entries, h->current), shapes, true); return true; } #endif