feat: add undo and redo logic for eraser stroke

This commit is contained in:
dscyrescotti
2024-06-10 22:44:55 +07:00
parent 2ef8956ad2
commit 7d52733d5d
6 changed files with 68 additions and 21 deletions

View File

@@ -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<AnyStroke>(maxEntries: 8)
var eraserStrokes: Set<EraserStroke> = []
@@ -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
}

View File

@@ -34,6 +34,7 @@ final class EraserStroke: Stroke, @unchecked Sendable {
weak var graphicContext: GraphicContext?
var finishesSaving: Bool = false
var penStrokes: Set<PenStroke> = []
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)

View File

@@ -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

View File

@@ -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()
}
}
}

View File

@@ -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

View File

@@ -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