Skip to content

Commit abdbc0a

Browse files
committed
Restore undo/redo functionality
1 parent 632134d commit abdbc0a

File tree

5 files changed

+172
-21
lines changed

5 files changed

+172
-21
lines changed

src/Document.zig

Lines changed: 148 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,41 @@ const Layer = struct {
5858
}
5959
self.cels.deinit(allocator);
6060
}
61+
62+
fn clone(self: Layer, allocator: Allocator) !Layer {
63+
var layer = Layer{
64+
.cels = try ArrayListUnmanaged(Cel).initCapacity(allocator, self.cels.items.len),
65+
.visible = self.visible,
66+
.locked = self.locked,
67+
.linked = self.linked,
68+
};
69+
for (self.cels.items) |*cel| {
70+
layer.cels.appendAssumeCapacity(Cel{
71+
.bitmap = if (cel.bitmap) |bitmap| try bitmap.clone(allocator) else null,
72+
.linked_frame = cel.linked_frame,
73+
});
74+
}
75+
return layer;
76+
}
77+
78+
fn eql(self: Layer, layer: Layer) bool {
79+
if (self.visible != layer.visible or
80+
self.locked != layer.locked or
81+
self.linked != layer.linked or
82+
self.cels.items.len != layer.cels.items.len)
83+
{
84+
return false;
85+
}
86+
for (self.cels.items, layer.cels.items) |*a, *b| {
87+
if (a.linked_frame != b.linked_frame) return false;
88+
if (a.bitmap != null and b.bitmap != null) {
89+
if (!a.bitmap.?.eql(b.bitmap.?)) return false;
90+
} else if (a.bitmap != null or b.bitmap != null) {
91+
return false;
92+
}
93+
}
94+
return true;
95+
}
6196
};
6297

6398
const BitmapIterator = struct {
@@ -276,7 +311,7 @@ pub fn save(self: *Self, file_path: []const u8) !void {
276311
try image.writeToFile(file_path);
277312
}
278313

279-
const Snapshot = struct {
314+
pub const Snapshot = struct {
280315
x: i32,
281316
y: i32,
282317
width: u32,
@@ -292,16 +327,122 @@ const Snapshot = struct {
292327
rect: Recti,
293328
bitmap: Bitmap,
294329
},
330+
331+
pub fn deinit(self: *Snapshot, allocator: Allocator) void {
332+
for (self.layers) |*layer| {
333+
layer.deinit(allocator);
334+
}
335+
allocator.free(self.layers);
336+
allocator.free(self.colormap);
337+
if (self.selection) |selection| {
338+
selection.bitmap.deinit(allocator);
339+
}
340+
allocator.destroy(self);
341+
}
295342
};
296343

297-
pub fn serialize(self: Document) ![]u8 {
298-
_ = self;
299-
return &.{};
344+
pub fn takeSnapshot(self: Document) !*Snapshot {
345+
const snapshot = try self.allocator.create(Snapshot);
346+
snapshot.* = .{
347+
.x = self.x,
348+
.y = self.y,
349+
.width = self.width,
350+
.height = self.height,
351+
.bitmap_type = self.bitmap_type,
352+
.frame_count = self.frame_count,
353+
.frame_time = self.frame_time,
354+
.selected_frame = self.selected_frame,
355+
.selected_layer = self.selected_layer,
356+
.layers = try self.allocator.alloc(Layer, self.layers.items.len),
357+
.colormap = try self.allocator.dupe(u8, self.colormap),
358+
.selection = null,
359+
};
360+
for (self.layers.items, 0..) |layer, i| {
361+
snapshot.layers[i] = try layer.clone(self.allocator);
362+
}
363+
std.mem.copy(u8, snapshot.colormap, self.colormap);
364+
if (self.selection) |selection| {
365+
snapshot.selection = .{
366+
.rect = selection.rect,
367+
.bitmap = try selection.bitmap.clone(self.allocator),
368+
};
369+
}
370+
return snapshot;
300371
}
301372

302-
pub fn deserialize(self: *Document, data: []u8) !void {
303-
_ = self;
304-
_ = data;
373+
pub fn fromSnapshot(self: *Document, snapshot: *Snapshot) !void {
374+
self.width = self.width;
375+
self.height = self.height;
376+
self.frame_count = snapshot.frame_count;
377+
self.frame_time = snapshot.frame_time;
378+
self.bitmap_type = snapshot.bitmap_type;
379+
self.selected_frame = snapshot.selected_frame;
380+
self.selected_layer = snapshot.selected_layer;
381+
for (self.layers.items) |*layer| {
382+
layer.deinit(self.allocator);
383+
}
384+
self.layers.clearRetainingCapacity();
385+
try self.layers.ensureTotalCapacity(snapshot.layers.len);
386+
for (snapshot.layers) |layer| {
387+
self.layers.appendAssumeCapacity(try layer.clone(self.allocator));
388+
}
389+
self.allocator.free(self.colormap);
390+
self.colormap = try self.allocator.dupe(u8, snapshot.colormap);
391+
self.freeSelection();
392+
if (snapshot.selection) |selection| {
393+
self.selection = Selection{
394+
.rect = selection.rect,
395+
.bitmap = try selection.bitmap.clone(self.allocator),
396+
};
397+
self.need_selection_texture_recreation = true;
398+
}
399+
400+
self.preview_bitmap.deinit(self.allocator);
401+
self.preview_bitmap = try Bitmap.init(self.allocator, self.width, self.height, self.bitmap_type);
402+
self.preview_bitmap.clear();
403+
self.need_texture_recreation = true;
404+
405+
if (self.x != snapshot.x or self.y != snapshot.y) {
406+
const dx = snapshot.x - self.x;
407+
const dy = snapshot.y - self.y;
408+
self.x += dx;
409+
self.y += dy;
410+
self.canvas.translateByPixel(dx, dy);
411+
}
412+
self.last_preview = .full;
413+
self.clearPreview();
414+
}
415+
416+
pub fn eqlSnapshot(self: Document, snapshot: *Snapshot) bool {
417+
if (self.x != snapshot.x or
418+
self.y != snapshot.y or
419+
self.width != snapshot.width or
420+
self.height != snapshot.height or
421+
self.bitmap_type != snapshot.bitmap_type or
422+
self.frame_count != snapshot.frame_count or
423+
self.frame_time != snapshot.frame_time or
424+
self.selected_frame != snapshot.selected_frame or
425+
self.selected_layer != snapshot.selected_layer or
426+
self.layers.items.len != snapshot.layers.len or
427+
!std.mem.eql(u8, self.colormap, snapshot.colormap))
428+
{
429+
return false;
430+
}
431+
432+
// compare layers
433+
for (self.layers.items, 0..) |layer, i| {
434+
if (!layer.eql(snapshot.layers[i])) return false;
435+
}
436+
437+
// compare selection
438+
if (self.selection != null and snapshot.selection != null) {
439+
if (!self.selection.?.rect.eql(snapshot.selection.?.rect)) return false;
440+
if (!self.selection.?.bitmap.eql(snapshot.selection.?.bitmap)) return false;
441+
} else if (self.selection != null or snapshot.selection != null) {
442+
return false;
443+
}
444+
445+
return true;
305446
}
306447

307448
fn getCel(self: *Self, layer: u32, frame: u32) *Cel {

src/EditorWidget.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ pub fn init(allocator: Allocator, rect: Rect(f32), vg: nvg) !*Self {
213213
configureToolbarButton(self.palette_toggle_button, icons.iconColorPalette, tryTogglePalette, "Toggle between 8-bit indexed mode and true color");
214214

215215
std.mem.copy(u8, self.document.colormap, &self.color_palette.colors);
216-
try self.document.history.reset(self.document); // so palette is part of first snapshot
217216
self.color_palette.onSelectionChangedFn = struct {
218217
fn selectionChanged(color_palette: *ColorPaletteWidget) void {
219218
if (color_palette.widget.parent) |parent| {
@@ -339,6 +338,7 @@ pub fn init(allocator: Allocator, rect: Rect(f32), vg: nvg) !*Self {
339338
}.onResize;
340339

341340
self.document.history.editor = self; // Register for updates
341+
try self.document.history.reset(self.document);
342342

343343
self.updateLayout();
344344
self.setTool(.draw);

src/bitmap.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ pub const Bitmap = union(BitmapType) {
4848
};
4949
}
5050

51+
pub fn eql(self: Bitmap, bitmap: Bitmap) bool {
52+
if (self.getType() != bitmap.getType()) return false;
53+
return switch (self) {
54+
.color => |color_bitmap| color_bitmap.eql(bitmap.color),
55+
.indexed => |indexed_bitmap| indexed_bitmap.eql(bitmap.indexed),
56+
};
57+
}
58+
5159
pub fn clear(self: Bitmap) void {
5260
switch (self) {
5361
inline else => |bitmap| bitmap.clear(),

src/gui/geometry.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ pub fn Rect(comptime T: type) type {
8787
return .{ .x = x, .y = y, .w = w, .h = h };
8888
}
8989

90+
pub fn eql(self: Self, other: Self) bool {
91+
return self.x == other.x and self.y == other.y and self.w == other.w and self.h == other.h;
92+
}
93+
9094
pub fn fromPoints(p0: Point(T), p1: Point(T)) Self {
9195
return .{
9296
.x = @min(p0.x, p1.x),

src/history.zig

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ const ArrayList = std.ArrayList;
44

55
const EditorWidget = @import("EditorWidget.zig");
66
const Document = @import("Document.zig");
7-
8-
const Snapshot = []u8;
7+
const Snapshot = Document.Snapshot;
98

109
pub const Buffer = struct {
1110
allocator: Allocator,
12-
stack: ArrayList(Snapshot),
11+
stack: ArrayList(*Snapshot),
1312
index: usize = 0,
1413

1514
editor: ?*EditorWidget = null,
@@ -18,30 +17,30 @@ pub const Buffer = struct {
1817
var self = try allocator.create(Buffer);
1918
self.* = Buffer{
2019
.allocator = allocator,
21-
.stack = ArrayList(Snapshot).init(allocator),
20+
.stack = ArrayList(*Snapshot).init(allocator),
2221
};
2322
return self;
2423
}
2524

2625
pub fn deinit(self: *Buffer) void {
2726
for (self.stack.items) |snapshot| {
28-
self.allocator.free(snapshot);
27+
snapshot.deinit(self.allocator);
2928
}
3029
self.stack.deinit();
3130
self.allocator.destroy(self);
3231
}
3332

3433
pub fn clearAndFreeStack(self: *Buffer) void {
3534
for (self.stack.items) |snapshot| {
36-
self.allocator.free(snapshot);
35+
snapshot.deinit(self.allocator);
3736
}
3837
self.stack.shrinkRetainingCapacity(0);
3938
self.index = 0;
4039
}
4140

4241
pub fn reset(self: *Buffer, document: *Document) !void {
4342
self.clearAndFreeStack();
44-
try self.stack.append(try document.serialize());
43+
try self.stack.append(try document.takeSnapshot());
4544
self.notifyChanged(document);
4645
}
4746

@@ -57,7 +56,7 @@ pub const Buffer = struct {
5756
if (!self.canUndo()) return;
5857
self.index -= 1;
5958
const snapshot = self.stack.items[self.index];
60-
try document.deserialize(snapshot);
59+
try document.fromSnapshot(snapshot);
6160
self.notifyChanged(document);
6261
}
6362

@@ -69,23 +68,22 @@ pub const Buffer = struct {
6968
if (!self.canRedo()) return;
7069
self.index += 1;
7170
const snapshot = self.stack.items[self.index];
72-
try document.deserialize(snapshot);
71+
try document.fromSnapshot(snapshot);
7372
self.notifyChanged(document);
7473
}
7574

7675
pub fn pushFrame(self: *Buffer, document: *Document) !void { // TODO: handle error cases
7776
// do comparison
7877
const top = self.stack.items[self.index];
79-
const snapshot = try document.serialize();
80-
if (std.mem.eql(u8, top, snapshot)) {
81-
document.allocator.free(snapshot);
78+
if (document.eqlSnapshot(top)) {
8279
return;
8380
}
8481

82+
const snapshot = try document.takeSnapshot();
8583
self.index += 1;
8684
// clear redo stack
8785
for (self.stack.items[self.index..self.stack.items.len]) |snap| {
88-
document.allocator.free(snap);
86+
snap.deinit(self.allocator);
8987
}
9088
self.stack.shrinkRetainingCapacity(self.index);
9189

0 commit comments

Comments
 (0)