You've already forked flecs_tests
296 lines
9.8 KiB
C
296 lines
9.8 KiB
C
#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
|