diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index 20da0fc..9467fc1 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -10,7 +10,6 @@ import MetalKit import CoreData import Foundation -#warning("TODO: to update history undo and redo logic") final class GraphicContext: @unchecked Sendable { var tree: RTree = RTree(maxEntries: 8) var eraserStrokes: Set = [] @@ -46,26 +45,59 @@ final class GraphicContext: @unchecked Sendable { func undoGraphic(for event: HistoryEvent) { switch event { case .stroke(let stroke): - guard let _stroke = stroke.stroke(as: PenStroke.self) else { return } - let deletedStroke = tree.remove(_stroke.anyStroke, in: _stroke.strokeBox) - withPersistence(\.backgroundContext) { [stroke = deletedStroke] context in - stroke?.stroke(as: PenStroke.self)?.object?.graphicContext = nil - try context.saveIfNeeded() + switch stroke.style { + case .marker: + guard let penStroke = stroke.stroke(as: PenStroke.self) else { return } + let deletedStroke = tree.remove(penStroke.anyStroke, in: penStroke.strokeBox) + withPersistence(\.backgroundContext) { [stroke = deletedStroke] context in + stroke?.stroke(as: PenStroke.self)?.object?.graphicContext = nil + try context.saveIfNeeded() + } + case .eraser: + guard let eraserStroke = stroke.stroke(as: EraserStroke.self) else { return } + eraserStrokes.remove(eraserStroke) + let penStrokes = eraserStroke.penStrokes + withPersistence(\.backgroundContext) { [penStrokes] context in + for penStroke in penStrokes { + penStroke.eraserStrokes.remove(eraserStroke) + if let object = eraserStroke.object { + penStroke.object?.erasers.remove(object) + } + } + } } previousStroke = nil } - } func redoGraphic(for event: HistoryEvent) { switch event { case .stroke(let stroke): - if let stroke = stroke.stroke(as: PenStroke.self) { - tree.insert(stroke.anyStroke, in: stroke.strokeBox) - } - withPersistence(\.backgroundContext) { [weak self, stroke] context in - stroke.stroke(as: PenStroke.self)?.object?.graphicContext = self?.object - try context.saveIfNeeded() + switch stroke.style { + case .marker: + guard let penStroke = stroke.stroke(as: PenStroke.self) else { + break + } + tree.insert(penStroke.anyStroke, in: penStroke.strokeBox) + withPersistence(\.backgroundContext) { [weak self, penStroke] context in + penStroke.object?.graphicContext = self?.object + try context.saveIfNeeded() + } + case .eraser: + guard let eraserStroke = stroke.stroke(as: EraserStroke.self) else { + break + } + eraserStrokes.insert(eraserStroke) + let penStrokes = eraserStroke.penStrokes + withPersistence(\.backgroundContext) { [eraserStroke, penStrokes] context in + for penStroke in penStrokes { + penStroke.eraserStrokes.insert(eraserStroke) + if let object = eraserStroke.object { + penStroke.object?.erasers.add(object) + } + } + try context.saveIfNeeded() + } } previousStroke = nil } diff --git a/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift b/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift index f96f3df..06bf90a 100644 --- a/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift +++ b/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift @@ -34,6 +34,7 @@ final class EraserStroke: Stroke, @unchecked Sendable { weak var graphicContext: GraphicContext? var finishesSaving: Bool = false + var penStrokes: Set = [] init( bounds: [CGFloat], @@ -58,7 +59,7 @@ final class EraserStroke: Stroke, @unchecked Sendable { bounds: object.bounds, color: object.color, style: style, - createdAt: object.createdAt, + createdAt: object.createdAt ?? .now, thickness: object.thickness ) self.object = object @@ -111,6 +112,7 @@ final class EraserStroke: Stroke, @unchecked Sendable { for stroke in graphicContext.tree.search(box: _quad.quadBox) { if let _penStroke = stroke.stroke(as: PenStroke.self), !_penStroke.eraserStrokes.contains(self) { _penStroke.eraserStrokes.insert(self) + penStrokes.insert(_penStroke) if let penStroke = _penStroke.object { penStroke.erasers.add(eraser) eraser.strokes.add(penStroke) diff --git a/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift b/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift index 68f669f..81f9a67 100644 --- a/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift +++ b/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift @@ -61,11 +61,12 @@ final class PenStroke: Stroke, @unchecked Sendable { convenience init(object: StrokeObject) { let style = StrokeStyle(rawValue: object.style) ?? .marker + #warning("TODO: revisit here and check if there is any crash") self.init( bounds: object.bounds, color: object.color, style: style, - createdAt: object.createdAt, // sometimes crash here + createdAt: object.createdAt ?? .now, // sometimes crash here thickness: object.thickness ) self.object = object diff --git a/Memola/Canvas/History/History.swift b/Memola/Canvas/History/History.swift index 825f357..6125381 100644 --- a/Memola/Canvas/History/History.swift +++ b/Memola/Canvas/History/History.swift @@ -52,11 +52,23 @@ class History: ObservableObject { for event in redoStack { switch event { case .stroke(let _stroke): - withPersistence(\.backgroundContext) { context in - if let stroke = _stroke.stroke(as: PenStroke.self)?.object { - context.delete(stroke) + switch _stroke.style { + case .marker: + guard let penStroke = _stroke.stroke(as: PenStroke.self) else { return } + withPersistence(\.backgroundContext) { context in + if let stroke = penStroke.object { + context.delete(stroke) + } + try context.saveIfNeeded() + } + case .eraser: + guard let eraserStroke = _stroke.stroke(as: EraserStroke.self) else { return } + withPersistence(\.backgroundContext) { context in + if let stroke = eraserStroke.object { + context.delete(stroke) + } + try context.saveIfNeeded() } - try context.saveIfNeeded() } } } diff --git a/Memola/Persistence/Objects/EraserObject.swift b/Memola/Persistence/Objects/EraserObject.swift index 7c5ac48..ad8f648 100644 --- a/Memola/Persistence/Objects/EraserObject.swift +++ b/Memola/Persistence/Objects/EraserObject.swift @@ -13,7 +13,7 @@ final class EraserObject: NSManagedObject { @NSManaged var bounds: [CGFloat] @NSManaged var color: [CGFloat] @NSManaged var style: Int16 - @NSManaged var createdAt: Date + @NSManaged var createdAt: Date? @NSManaged var thickness: CGFloat @NSManaged var quads: NSMutableOrderedSet @NSManaged var strokes: NSMutableSet diff --git a/Memola/Persistence/Objects/StrokeObject.swift b/Memola/Persistence/Objects/StrokeObject.swift index a90f24a..e304f46 100644 --- a/Memola/Persistence/Objects/StrokeObject.swift +++ b/Memola/Persistence/Objects/StrokeObject.swift @@ -13,7 +13,7 @@ final class StrokeObject: NSManagedObject { @NSManaged var bounds: [CGFloat] @NSManaged var color: [CGFloat] @NSManaged var style: Int16 - @NSManaged var createdAt: Date + @NSManaged var createdAt: Date? @NSManaged var thickness: CGFloat @NSManaged var quads: NSMutableOrderedSet @NSManaged var erasers: NSMutableSet