bool skip;
/** Some situations require the global state to be stored, edge cases when exiting modes. */
bool use_memfile_step;
+ /** For use by undo systems that accumulate changes (text editor, painting). */
+ bool is_applied;
/* Over alloc 'type->struct_size'. */
} UndoStep;
-typedef enum eUndoTypeMode {
- /**
- * Each undo step stores a version of the state.
- * This means we can simply load in a previous state at any time.
- */
- BKE_UNDOTYPE_MODE_STORE = 1,
- /**
- * Each undo step is a series of edits.
- * This means to change states we need to apply each edit.
- * It also means the 'step_decode' callback needs to detect the difference between undo and redo.
- * (Currently used for text edit and image & sculpt painting).
- */
- BKE_UNDOTYPE_MODE_ACCUMULATE = 2,
-} eUndoTypeMode;
-
typedef void (*UndoTypeForEachIDRefFn)(void *user_data, struct UndoRefID *id_ref);
typedef struct UndoType {
void (*step_foreach_ID_ref)(UndoStep *us, UndoTypeForEachIDRefFn foreach_ID_ref_fn, void *user_data);
- eUndoTypeMode mode;
bool use_context;
int step_size;
extern const UndoType *BKE_UNDOSYS_TYPE_SCULPT;
extern const UndoType *BKE_UNDOSYS_TYPE_TEXT;
+#define BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(ty) \
+ ELEM(ty, BKE_UNDOSYS_TYPE_IMAGE)
+
UndoStack *BKE_undosys_stack_create(void);
void BKE_undosys_stack_destroy(UndoStack *ustack);
void BKE_undosys_stack_clear(UndoStack *ustack);
void BKE_undosys_stack_init_from_context(UndoStack *ustack, bContext *C)
{
const UndoType *ut = BKE_undosys_type_from_context(C);
- if ((ut != NULL) && (ut != BKE_UNDOSYS_TYPE_MEMFILE) && (ut->mode == BKE_UNDOTYPE_MODE_STORE)) {
+ if ((ut != NULL) && (ut != BKE_UNDOSYS_TYPE_MEMFILE)) {
BKE_undosys_step_push_with_type(ustack, C, "original mode", ut);
}
}
if (ustack->step_active) {
UndoStep *us_iter = ustack->step_active;
while (us_iter != us) {
- if (us_iter->type->mode == BKE_UNDOTYPE_MODE_ACCUMULATE) {
- undosys_step_decode(C, G_MAIN, ustack, us_iter, -1);
- }
+ /* TODO:
+ * - skip successive steps that store the same data, eg: memfile steps.
+ * - or steps that include another steps data, eg: a memfile step includes text undo data.
+ */
+ undosys_step_decode(C, G_MAIN, ustack, us_iter, -1);
us_iter = us_iter->prev;
}
}
- if (us->type->mode != BKE_UNDOTYPE_MODE_ACCUMULATE) {
- undosys_step_decode(C, G_MAIN, ustack, us, -1);
- }
+ undosys_step_decode(C, G_MAIN, ustack, us, -1);
+
ustack->step_active = us_prev;
undosys_stack_validate(ustack, true);
if (use_skip) {
if (ustack->step_active && ustack->step_active->next) {
UndoStep *us_iter = ustack->step_active->next;
while (us_iter != us) {
- if (us_iter->type->mode == BKE_UNDOTYPE_MODE_ACCUMULATE) {
- undosys_step_decode(C, G_MAIN, ustack, us_iter, 1);
- }
+ undosys_step_decode(C, G_MAIN, ustack, us_iter, 1);
us_iter = us_iter->next;
}
}
- /* Unlike undo, always redo accumulation state. */
undosys_step_decode(C, G_MAIN, ustack, us, 1);
ustack->step_active = us_next;
if (use_skip) {
undosys_fn(ut);
- BLI_assert(ut->mode != 0);
-
BLI_addtail(&g_undo_types, ut);
return ut;
void BKE_undosys_print(UndoStack *ustack)
{
- printf("Undo %d Steps (A: active, M=memfile-active, S=skip)\n",
+ printf("Undo %d Steps (*: active, #=applied, M=memfile-active, S=skip)\n",
BLI_listbase_count(&ustack->steps));
int index = 0;
for (UndoStep *us = ustack->steps.first; us; us = us->next) {
- printf("[%c%c%c] %3d type='%s', name='%s'\n",
- (us == ustack->step_active) ? 'A' : '_',
- (us == ustack->step_active_memfile) ? 'M' : '_',
- us->skip ? 'S' : '_',
+ printf("[%c%c%c%c] %3d type='%s', name='%s'\n",
+ (us == ustack->step_active) ? '*' : ' ',
+ us->is_applied ? '#' : ' ',
+ (us == ustack->step_active_memfile) ? 'M' : ' ',
+ us->skip ? 'S' : ' ',
index,
us->type->name,
us->name);
ut->step_foreach_ID_ref = armature_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(ArmatureUndoStep);
ut->step_foreach_ID_ref = curve_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(CurveUndoStep);
ut->step_foreach_ID_ref = font_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(FontUndoStep);
void ED_imapaint_bucket_fill(struct bContext *C, float color[3], struct wmOperator *op);
/* paint_image_undo.c */
-void ED_image_undo_push_begin(const char *name);
+void ED_image_undo_push_begin(const char *name, int paint_mode);
void ED_image_undo_push_end(void);
void ED_image_undo_restore(struct UndoStep *us);
ut->step_foreach_ID_ref = lattice_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(LatticeUndoStep);
ut->step_foreach_ID_ref = mesh_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(MeshUndoStep);
ut->step_foreach_ID_ref = mball_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(MBallUndoStep);
ut->step_foreach_ID_ref = particle_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(ParticleUndoStep);
ut->step_decode = paintcurve_undosys_step_decode;
ut->step_free = paintcurve_undosys_step_free;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = false;
ut->step_size = sizeof(PaintCurveUndoStep);
}
settings->imapaint.flag |= IMAGEPAINT_DRAWING;
- ED_image_undo_push_begin(op->type->name);
+ ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D);
return pop;
}
BKE_undosys_step_push_init_with_type(wm->undo_stack, C, op->type->name, BKE_UNDOSYS_TYPE_IMAGE);
- ED_image_undo_push_begin(op->type->name);
+ ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D);
paint_2d_bucket_fill(C, color, NULL, NULL, NULL);
scene->toolsettings->imapaint.flag |= IMAGEPAINT_DRAWING;
- ED_image_undo_push_begin(op->type->name);
+ ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_3D);
/* allocate and initialize spatial data structures */
project_paint_begin(C, &ps, false, 0);
#include "BKE_context.h"
#include "BKE_image.h"
+#include "BKE_paint.h"
#include "BKE_undo_system.h"
#include "DEG_depsgraph.h"
#include "ED_paint.h"
#include "ED_undo.h"
#include "ED_util.h"
+#include "ED_object.h"
#include "GPU_draw.h"
}
}
-void ED_image_undo_push_begin(const char *name)
-{
- UndoStack *ustack = ED_undo_stack_get();
- bContext *C = NULL; /* special case, we never read from this. */
- BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE);
-}
-
-void ED_image_undo_push_end(void)
-{
- UndoStack *ustack = ED_undo_stack_get();
- BKE_undosys_step_push(ustack, NULL, NULL);
-}
-
static void image_undo_invalidate(void)
{
UndoImageTile *tile;
typedef struct ImageUndoStep {
UndoStep step;
ListBase tiles;
+ bool is_encode_init;
+ ePaintMode paint_mode;
/* Use for all ID lookups (can be NULL). */
struct UndoIDPtrMap *id_map;
{
ImageUndoStep *us = (ImageUndoStep *)us_p;
/* dummy, memory is cleared anyway. */
+ us->is_encode_init = true;
BLI_listbase_clear(&us->tiles);
}
-static bool image_undosys_step_encode(struct bContext *UNUSED(C), struct Main *UNUSED(bmain), UndoStep *us_p)
+static bool image_undosys_step_encode(struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p)
{
/* dummy, encoding is done along the way by adding tiles
* to the current 'ImageUndoStep' added by encode_init. */
int allocsize = IMAPAINT_TILE_SIZE * IMAPAINT_TILE_SIZE * 4;
-
- /* first dispose of invalid tiles (may happen due to drag dot for instance) */
- for (UndoImageTile *tile = us->tiles.first; tile;) {
- if (!tile->valid) {
- UndoImageTile *tmp_tile = tile->next;
- MEM_freeN(tile->rect.pt);
- BLI_freelinkN(&us->tiles, tile);
- tile = tmp_tile;
- }
- else {
- us->step.data_size += allocsize * ((tile->use_float) ? sizeof(float) : sizeof(char));
- tile = tile->next;
+ if (us->is_encode_init) {
+ /* first dispose of invalid tiles (may happen due to drag dot for instance) */
+ for (UndoImageTile *tile = us->tiles.first; tile;) {
+ if (!tile->valid) {
+ UndoImageTile *tmp_tile = tile->next;
+ MEM_freeN(tile->rect.pt);
+ BLI_freelinkN(&us->tiles, tile);
+ tile = tmp_tile;
+ }
+ else {
+ us->step.data_size += allocsize * ((tile->use_float) ? sizeof(float) : sizeof(char));
+ tile = tile->next;
+ }
}
}
+ else {
+ /* Happens when switching modes. */
+ ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C);
+ BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D));
+ us->paint_mode = paint_mode;
+ }
image_undosys_step_encode_store_ids(us);
+ us_p->is_applied = true;
+
return true;
}
-static void image_undosys_step_decode(struct bContext *UNUSED(C), struct Main *bmain, UndoStep *us_p, int UNUSED(dir))
+
+static void image_undosys_step_decode_undo_impl(ImageUndoStep *us)
+{
+ BLI_assert(us->step.is_applied == true);
+ image_undo_restore_list(&us->tiles, us->id_map);
+ us->step.is_applied = false;
+}
+
+static void image_undosys_step_decode_redo_impl(ImageUndoStep *us)
+{
+ BLI_assert(us->step.is_applied == false);
+ image_undo_restore_list(&us->tiles, us->id_map);
+ us->step.is_applied = true;
+}
+
+static void image_undosys_step_decode_undo(ImageUndoStep *us)
+{
+ ImageUndoStep *us_iter = us;
+ while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
+ if (us_iter->step.next->is_applied == false) {
+ break;
+ }
+ us_iter = (ImageUndoStep *)us_iter->step.next;
+ }
+ while (us_iter != us) {
+ image_undosys_step_decode_undo_impl(us_iter);
+ us_iter = (ImageUndoStep *)us_iter->step.prev;
+ }
+}
+
+static void image_undosys_step_decode_redo(ImageUndoStep *us)
+{
+ ImageUndoStep *us_iter = us;
+ while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
+ if (us_iter->step.prev->is_applied == true) {
+ break;
+ }
+ us_iter = (ImageUndoStep *)us_iter->step.prev;
+ }
+ while (us_iter && (us_iter->step.is_applied == false)) {
+ image_undosys_step_decode_redo_impl(us_iter);
+ if (us_iter == us) {
+ break;
+ }
+ us_iter = (ImageUndoStep *)us_iter->step.next;
+ }
+}
+
+static void image_undosys_step_decode(struct bContext *C, struct Main *bmain, UndoStep *us_p, int dir)
{
ImageUndoStep *us = (ImageUndoStep *)us_p;
#if 0
paint_undosys_step_decode_restore_ids(us);
#endif
- image_undo_restore_list(&us->tiles, us->id_map);
+
+ if (dir < 0) {
+ image_undosys_step_decode_undo(us);
+ }
+ else {
+ image_undosys_step_decode_redo(us);
+ }
+
+ if (us->paint_mode == PAINT_MODE_TEXTURE_3D) {
+ ED_object_mode_set(C, OB_MODE_TEXTURE_PAINT);
+ }
/* Refresh texture slots. */
ED_editors_init_for_undo(bmain);
ut->step_foreach_ID_ref = image_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_ACCUMULATE;
ut->use_context = true;
ut->step_size = sizeof(ImageUndoStep);
ListBase *ED_image_undo_get_tiles(void)
{
UndoStack *ustack = ED_undo_stack_get();
- UndoStep *us = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE);
- return ED_image_undosys_step_get_tiles(us);
+ UndoStep *us_prev = ustack->step_init;
+ UndoStep *us_p = BKE_undosys_stack_init_or_active_with_type(ustack, BKE_UNDOSYS_TYPE_IMAGE);
+ ImageUndoStep *us = (ImageUndoStep *)us_p;
+ /* We should always have an undo push started when accessing tiles,
+ * not doing this means we won't have paint_mode correctly set. */
+ BLI_assert(us_p == us_prev);
+ if (us_p != us_prev) {
+ /* Fallback value until we can be sure this never happens. */
+ us->paint_mode = PAINT_MODE_TEXTURE_2D;
+ }
+ return ED_image_undosys_step_get_tiles(us_p);
}
/* restore painting image to previous state. Used for anchored and drag-dot style brushes*/
image_undo_invalidate();
}
+void ED_image_undo_push_begin(const char *name, int paint_mode)
+{
+ UndoStack *ustack = ED_undo_stack_get();
+ bContext *C = NULL; /* special case, we never read from this. */
+ UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE);
+ ImageUndoStep *us = (ImageUndoStep *)us_p;
+ BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D));
+ us->paint_mode = paint_mode;
+}
+
+void ED_image_undo_push_end(void)
+{
+ UndoStack *ustack = ED_undo_stack_get();
+ BKE_undosys_step_push(ustack, NULL, NULL);
+}
+
/** \} */
if (unode && unode->type == SCULPT_UNDO_DYNTOPO_END) {
us->step.use_memfile_step = true;
}
+ us->step.is_applied = true;
return true;
}
-static void sculpt_undosys_step_decode(struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p, int UNUSED(dir))
+static void sculpt_undosys_step_decode_undo_impl(struct bContext *C, SculptUndoStep *us)
+{
+ BLI_assert(us->step.is_applied == true);
+ sculpt_undo_restore_list(C, &us->data.nodes);
+ us->step.is_applied = false;
+}
+
+static void sculpt_undosys_step_decode_redo_impl(struct bContext *C, SculptUndoStep *us)
+{
+ BLI_assert(us->step.is_applied == false);
+ sculpt_undo_restore_list(C, &us->data.nodes);
+ us->step.is_applied = true;
+}
+
+static void sculpt_undosys_step_decode_undo(struct bContext *C, SculptUndoStep *us)
+{
+ SculptUndoStep *us_iter = us;
+ while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
+ if (us_iter->step.next->is_applied == false) {
+ break;
+ }
+ us_iter = (SculptUndoStep *)us_iter->step.next;
+ }
+ while (us_iter != us) {
+ sculpt_undosys_step_decode_undo_impl(C, us_iter);
+ us_iter = (SculptUndoStep *)us_iter->step.prev;
+ }
+}
+
+static void sculpt_undosys_step_decode_redo(struct bContext *C, SculptUndoStep *us)
+{
+ SculptUndoStep *us_iter = us;
+ while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
+ if (us_iter->step.prev->is_applied == true) {
+ break;
+ }
+ us_iter = (SculptUndoStep *)us_iter->step.prev;
+ }
+ while (us_iter && (us_iter->step.is_applied == false)) {
+ sculpt_undosys_step_decode_redo_impl(C, us_iter);
+ if (us_iter == us) {
+ break;
+ }
+ us_iter = (SculptUndoStep *)us_iter->step.next;
+ }
+}
+
+static void sculpt_undosys_step_decode(struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p, int dir)
{
/* TODO(campbell): undo_system: use low-level API to set mode. */
ED_object_mode_set(C, OB_MODE_SCULPT);
BLI_assert(sculpt_undosys_poll(C));
SculptUndoStep *us = (SculptUndoStep *)us_p;
- sculpt_undo_restore_list(C, &us->data.nodes);
+ if (dir < 0) {
+ sculpt_undosys_step_decode_undo(C, us);
+ }
+ else {
+ sculpt_undosys_step_decode_redo(C, us);
+ }
}
static void sculpt_undosys_step_free(UndoStep *us_p)
ut->step_decode = sculpt_undosys_step_decode;
ut->step_free = sculpt_undosys_step_free;
- ut->mode = BKE_UNDOTYPE_MODE_ACCUMULATE;
ut->use_context = true;
ut->step_size = sizeof(SculptUndoStep);
}
if (support_undo) {
- ED_image_undo_push_begin(op->type->name);
+ ED_image_undo_push_begin(op->type->name, PAINT_MODE_TEXTURE_2D);
/* not strictly needed, because we only imapaint_dirty_region to invalidate all tiles
* but better do this right in case someone copies this for a tool that uses partial
* redraw better */
return false;
}
+ us_p->is_applied = true;
+
us->text_ref.ptr = text;
us->step.data_size = us->data.len;
return true;
}
+
+static void text_undosys_step_decode_undo_impl(Text *text, TextUndoStep *us)
+{
+ BLI_assert(us->step.is_applied == true);
+ TextUndoBuf data = us->data;
+ while (data.pos > -1) {
+ txt_do_undo(text, &data);
+ }
+ BLI_assert(data.pos == -1);
+ us->step.is_applied = false;
+}
+
+static void text_undosys_step_decode_redo_impl(Text *text, TextUndoStep *us)
+{
+ BLI_assert(us->step.is_applied == false);
+ TextUndoBuf data = us->data;
+ data.pos = -1;
+ while (data.pos < us->data.pos) {
+ txt_do_redo(text, &data);
+ }
+ BLI_assert(data.pos == us->data.pos);
+ us->step.is_applied = true;
+}
+
+static void text_undosys_step_decode_undo(Text *text, TextUndoStep *us)
+{
+ TextUndoStep *us_iter = us;
+ while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) {
+ if (us_iter->step.next->is_applied == false) {
+ break;
+ }
+ us_iter = (TextUndoStep *)us_iter->step.next;
+ }
+ while (us_iter != us) {
+ text_undosys_step_decode_undo_impl(text, us_iter);
+ us_iter = (TextUndoStep *)us_iter->step.prev;
+ }
+}
+
+static void text_undosys_step_decode_redo(Text *text, TextUndoStep *us)
+{
+ TextUndoStep *us_iter = us;
+ while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) {
+ if (us_iter->step.prev->is_applied == true) {
+ break;
+ }
+ us_iter = (TextUndoStep *)us_iter->step.prev;
+ }
+ while (us_iter && (us_iter->step.is_applied == false)) {
+ text_undosys_step_decode_redo_impl(text, us_iter);
+ if (us_iter == us) {
+ break;
+ }
+ us_iter = (TextUndoStep *)us_iter->step.next;
+ }
+}
+
static void text_undosys_step_decode(struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p, int dir)
{
TextUndoStep *us = (TextUndoStep *)us_p;
Text *text = us->text_ref.ptr;
if (dir < 0) {
- TextUndoBuf data = us->data;
- while (data.pos > -1) {
- txt_do_undo(text, &data);
- }
- BLI_assert(data.pos == -1);
+ text_undosys_step_decode_undo(text, us);
}
else {
- TextUndoBuf data = us->data;
- data.pos = -1;
- while (data.pos < us->data.pos) {
- txt_do_redo(text, &data);
- }
- BLI_assert(data.pos == us->data.pos);
+ text_undosys_step_decode_redo(text, us);
}
SpaceText *st = CTX_wm_space_text(C);
ut->step_foreach_ID_ref = text_undosys_foreach_ID_ref;
- ut->mode = BKE_UNDOTYPE_MODE_ACCUMULATE;
ut->use_context = false;
ut->step_size = sizeof(TextUndoStep);
MemFileUndoStep *us = (MemFileUndoStep *)us_p;
BKE_memfile_undo_decode(us->data, C);
+ for (UndoStep *us_iter = us_p->next; us_iter; us_iter = us_iter->next) {
+ if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
+ continue;
+ }
+ us_iter->is_applied = false;
+ }
+ for (UndoStep *us_iter = us_p; us_iter; us_iter = us_iter->prev) {
+ if (BKE_UNDOSYS_TYPE_IS_MEMFILE_SKIP(us_iter->type)) {
+ continue;
+ }
+ us_iter->is_applied = true;
+ }
+
/* bmain has been freed. */
bmain = CTX_data_main(C);
ED_editors_init_for_undo(bmain);
ut->step_decode = memfile_undosys_step_decode;
ut->step_free = memfile_undosys_step_free;
- ut->mode = BKE_UNDOTYPE_MODE_STORE;
ut->use_context = true;
ut->step_size = sizeof(MemFileUndoStep);