diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index a060075..384123b 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; }; - EC771E602BEB6EE50053CC68 /* QuadValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC771E5F2BEB6EE50053CC68 /* QuadValueTransformer.swift */; }; EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; @@ -63,16 +62,15 @@ ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F12BE6128F00A4542E /* Collection++.swift */; }; ECA738F42BE612A000A4542E /* Array++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F32BE612A000A4542E /* Array++.swift */; }; ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; }; - ECA738F82BE612EB00A4542E /* StrokeQuad.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F72BE612EB00A4542E /* StrokeQuad.swift */; }; ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; }; ECA739012BE61D9C00A4542E /* MemolaModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FF2BE61D9C00A4542E /* MemolaModel.xcdatamodeld */; }; ECA739052BE61E3100A4542E /* Memo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739042BE61E3100A4542E /* Memo.swift */; }; ECA739082BE623F300A4542E /* PenToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenToolView.swift */; }; + ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECEC01A72BEE11BA006DA24C /* QuadShape.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = ""; }; - EC771E5F2BEB6EE50053CC68 /* QuadValueTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadValueTransformer.swift; sourceTree = ""; }; EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; }; EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -128,11 +126,11 @@ ECA738F12BE6128F00A4542E /* Collection++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = ""; }; ECA738F32BE612A000A4542E /* Array++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = ""; }; ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = ""; }; - ECA738F72BE612EB00A4542E /* StrokeQuad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeQuad.swift; sourceTree = ""; }; ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; ECA739002BE61D9C00A4542E /* MemolaModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MemolaModel.xcdatamodel; sourceTree = ""; }; ECA739042BE61E3100A4542E /* Memo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memo.swift; sourceTree = ""; }; ECA739072BE623F300A4542E /* PenToolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenToolView.swift; sourceTree = ""; }; + ECEC01A72BEE11BA006DA24C /* QuadShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadShape.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -162,14 +160,6 @@ path = ViewController; sourceTree = ""; }; - EC771E5C2BEB37FC0053CC68 /* Transformers */ = { - isa = PBXGroup; - children = ( - EC771E5F2BEB6EE50053CC68 /* QuadValueTransformer.swift */, - ); - path = Transformers; - sourceTree = ""; - }; EC7F6BDF2BE5E6E300A34A7B = { isa = PBXGroup; children = ( @@ -304,8 +294,8 @@ ECA738982BE6015700A4542E /* Primitives */ = { isa = PBXGroup; children = ( - ECA738F72BE612EB00A4542E /* StrokeQuad.swift */, EC4538882BEBCAE000A86FEC /* Quad.swift */, + ECEC01A72BEE11BA006DA24C /* QuadShape.swift */, ); path = Primitives; sourceTree = ""; @@ -463,7 +453,6 @@ ECA738FA2BE61B1700A4542E /* Persistence */ = { isa = PBXGroup; children = ( - EC771E5C2BEB37FC0053CC68 /* Transformers */, ECA739022BE61DE700A4542E /* Core */, ); path = Persistence; @@ -572,7 +561,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EC771E602BEB6EE50053CC68 /* QuadValueTransformer.swift in Sources */, ECA738B02BE60D0B00A4542E /* CanvasViewController.swift in Sources */, ECA738E42BE6110800A4542E /* Drawable.swift in Sources */, ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */, @@ -594,6 +582,7 @@ ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */, ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */, ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */, + ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */, ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */, ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */, ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */, @@ -628,7 +617,6 @@ EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */, ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */, ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */, - ECA738F82BE612EB00A4542E /* StrokeQuad.swift in Sources */, ECA738972BE6014200A4542E /* Graphic.metal in Sources */, ECA7388A2BE6006A00A4542E /* PipelineStates.swift in Sources */, ); diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index 119dd03..9f81d25 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -12,7 +12,7 @@ struct MemolaApp: App { var body: some Scene { WindowGroup { MemosView() - .environment(\.managedObjectContext, Persistence.shared.viewContext) + .environment(\.managedObjectContext, Persistence.context) } } } diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index ce2b2ef..6775eb7 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -44,11 +44,7 @@ final class GraphicContext: NSManagedObject { strokes.remove(stroke) stroke.graphicContext = nil previousStroke = nil - do { - try Persistence.shared.viewContext.save() - } catch { - NSLog("[Memola] - \(error.localizedDescription)") - } + Persistence.saveIfNeeded() } func redoGraphic(for event: HistoryEvent) { @@ -58,11 +54,7 @@ final class GraphicContext: NSManagedObject { stroke.graphicContext = self previousStroke = nil } - do { - try Persistence.shared.viewContext.save() - } catch { - NSLog("[Memola] - \(error.localizedDescription)") - } + Persistence.saveIfNeeded() } } @@ -84,13 +76,13 @@ extension GraphicContext: Drawable { extension GraphicContext { func beginStroke(at point: CGPoint, pen: Pen) -> Stroke { - let stroke = Stroke(context: Persistence.shared.viewContext) + let stroke = Stroke(context: Persistence.context) stroke.id = UUID() stroke.color = pen.color stroke.style = pen.strokeStyle.rawValue stroke.thickness = pen.thickness stroke.createdAt = .now - stroke.strokeQuads = [] + stroke.quads = [] stroke.graphicContext = self strokes.add(stroke) currentStroke = stroke @@ -109,12 +101,7 @@ extension GraphicContext { func endStroke(at point: CGPoint) { guard currentPoint != nil, let currentStroke else { return } currentStroke.finish(at: point) - currentStroke.saveQuads() - do { - try Persistence.shared.viewContext.save() - } catch { - NSLog("[Memola] - \(error.localizedDescription)") - } + Persistence.saveIfNeeded() previousStroke = currentStroke self.currentStroke = nil self.currentPoint = nil @@ -122,14 +109,11 @@ extension GraphicContext { func cancelStroke() { if let stroke = strokes.lastObject as? Stroke { - do { - let viewContext = Persistence.shared.viewContext + Persistence.performe { context in strokes.remove(stroke) - viewContext.delete(stroke) - try viewContext.save() - } catch { - NSLog("[Memola] - \(error.localizedDescription)") + context.delete(stroke) } + Persistence.saveIfNeeded() } currentStroke = nil currentPoint = nil diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index ba73052..c94e09a 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -43,26 +43,15 @@ final class Canvas: NSManagedObject, Identifiable { extension Canvas { func load() { let start = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5))) - Task(priority: .high) { [start] in - await MainActor.run { - state = .loading - objectWillChange.send() - } - await withTaskGroup(of: Void.self) { taskGroup in - for stroke in graphicContext.strokes { - guard let stroke = stroke as? Stroke else { continue } - taskGroup.addTask { - stroke.loadVertices() - } - } - } - let end = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5))) - NSLog("[Memola] - Loaded from \(start) to \(end)") - await MainActor.run { - state = .loaded - objectWillChange.send() + for stroke in graphicContext.strokes { + if let stroke = stroke as? Stroke { + stroke.loadVertices() + NSLog("[Memola] - \(stroke.quads.count) quads") } } + let end = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5))) + NSLog("[Memola] - Loaded from \(start) to \(end)") + state = .loaded } } diff --git a/Memola/Canvas/Geometries/Primitives/Quad.swift b/Memola/Canvas/Geometries/Primitives/Quad.swift index 9f46e04..f5f5742 100644 --- a/Memola/Canvas/Geometries/Primitives/Quad.swift +++ b/Memola/Canvas/Geometries/Primitives/Quad.swift @@ -5,37 +5,38 @@ // Created by Dscyre Scotti on 5/8/24. // +import CoreData import Foundation -struct Quad: Codable { - var origin: CGPoint - var color: [CGFloat] - var size: CGFloat - var rotation: CGFloat - var shape: QuadShape +@objc(Quad) +class Quad: NSManagedObject { + @NSManaged var id: UUID + @NSManaged var originX: CGFloat + @NSManaged var originY: CGFloat + @NSManaged var size: CGFloat + @NSManaged var rotation: CGFloat + @NSManaged var shape: Int16 + @NSManaged var stroke: Stroke? - init(origin: CGPoint, size: CGFloat, color: [CGFloat], rotation: CGFloat, shape: QuadShape = .rounded) { - self.origin = origin - self.size = size - self.color = color - self.rotation = rotation - self.shape = shape - } - - func generateVertices() -> [QuadVertex] { - switch shape { - case .rounded: - generateRoundedQuad() - case .squared: - generateSquaredQuad() - case let .calligraphic(vFactor, hFactor): - generateCalligraphicQuad(vFactor: vFactor, hFactor: hFactor) - case let .trapezoid(topFactor, bottomFactor, heightFactor): - generateTrapezoidQuad(topFactor: topFactor, bottomFactor: bottomFactor, heightFactor: heightFactor) + var origin: CGPoint { + get { CGPoint(x: originX, y: originY) } + set { + originX = newValue.x + originY = newValue.y } } - func generateRoundedQuad() -> [QuadVertex] { + func generateVertices(_ color: [CGFloat]) -> [QuadVertex] { + guard let shape = QuadShape.init(rawValue: shape) else { return [] } + switch shape { + case .rounded: + return generateRoundedQuad(color) + case .squared: + return generateSquaredQuad(color) + } + } + + func generateRoundedQuad(_ color: [CGFloat]) -> [QuadVertex] { let halfSize = size * 0.5 return [ QuadVertex(x: origin.x - halfSize, y: origin.y - halfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation), @@ -47,7 +48,7 @@ struct Quad: Codable { ] } - func generateSquaredQuad() -> [QuadVertex] { + func generateSquaredQuad(_ color: [CGFloat]) -> [QuadVertex] { let vHalfSize = size * 0.5 let hHalfSize = size * 0.15 return [ @@ -59,38 +60,4 @@ struct Quad: Codable { QuadVertex(x: origin.x + hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation) ] } - - func generateCalligraphicQuad(vFactor: CGFloat, hFactor: CGFloat) -> [QuadVertex] { - let vHalfSize = size * vFactor * 0.5 - let hHalfSize = size * hFactor * 0.5 - return [ - QuadVertex(x: origin.x - hHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x + hHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x - hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x + hHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x - hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x + hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation) - ] - } - - func generateTrapezoidQuad(topFactor: CGFloat, bottomFactor: CGFloat, heightFactor: CGFloat) -> [QuadVertex] { - let vHalfSize = size * heightFactor * 0.5 - let hTopHalfSize = size * topFactor * 0.5 - let hBottomHalfSize = size * bottomFactor * 0.5 - return [ - QuadVertex(x: origin.x - hTopHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x + hBottomHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x - hTopHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x + hBottomHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x - hTopHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation), - QuadVertex(x: origin.x + hBottomHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation) - ] - } -} - -enum QuadShape: Codable { - case rounded - case squared - case calligraphic(CGFloat, CGFloat) - case trapezoid(topFactor: CGFloat, bottomFactor: CGFloat, heightFactor: CGFloat) } diff --git a/Memola/Canvas/Geometries/Primitives/QuadShape.swift b/Memola/Canvas/Geometries/Primitives/QuadShape.swift new file mode 100644 index 0000000..07ed3e7 --- /dev/null +++ b/Memola/Canvas/Geometries/Primitives/QuadShape.swift @@ -0,0 +1,13 @@ +// +// QuadShape.swift +// Memola +// +// Created by Dscyre Scotti on 5/10/24. +// + +import Foundation + +enum QuadShape: Int16 { + case rounded + case squared +} diff --git a/Memola/Canvas/Geometries/Primitives/StrokeQuad.swift b/Memola/Canvas/Geometries/Primitives/StrokeQuad.swift deleted file mode 100644 index 23eae5b..0000000 --- a/Memola/Canvas/Geometries/Primitives/StrokeQuad.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// StrokeQuad.swift -// Memola -// -// Created by Dscyre Scotti on 5/4/24. -// - -import MetalKit -import Foundation - -final class StrokeQuad: NSObject, Codable { - var quad: Quad - - init(quad: Quad) { - self.quad = quad - } -} - diff --git a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift index d1a31a7..30d8c31 100644 --- a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift +++ b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift @@ -27,7 +27,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { let control = CGPoint.middle(p1: start, p2: end) addCurve(from: start, to: end, by: control, on: stroke) case 3: - discardVertices(upto: stroke.vertexIndex, on: stroke) + discardVertices(upto: stroke.vertexIndex, quadIndex: stroke.quadIndex, on: stroke) let index = stroke.keyPoints.count - 1 var start = stroke.keyPoints[index - 2] var end = CGPoint.middle(p1: stroke.keyPoints[index - 2], p2: stroke.keyPoints[index - 1]) @@ -62,7 +62,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { } private func smoothOutPath(on stroke: Stroke) { - discardVertices(upto: stroke.vertexIndex, on: stroke) + discardVertices(upto: stroke.vertexIndex, quadIndex: stroke.quadIndex, on: stroke) adjustPreviousKeyPoint(on: stroke) switch stroke.keyPoints.count { case 4: @@ -79,6 +79,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { let end = CGPoint.middle(p1: stroke.keyPoints[index - 1], p2: stroke.keyPoints[index]) addCurve(from: start, to: end, by: control, on: stroke) } + stroke.quadIndex = stroke.quads.count - 1 stroke.vertexIndex = stroke.vertices.endIndex - 1 } @@ -105,9 +106,8 @@ struct SolidPointStrokeGenerator: StrokeGenerator { case .random: rotation = CGFloat.random(in: 0...360) * .pi / 180 } - let quad = Quad(origin: point, size: stroke.thickness, color: stroke.color, rotation: rotation) - stroke._quads.append(quad) - stroke.vertices.append(contentsOf: quad.generateVertices()) + let quad = stroke.addQuad(at: point, rotation: rotation, shape: .rounded) + stroke.vertices.append(contentsOf: quad.generateVertices(stroke.color)) stroke.vertexCount = stroke.vertices.endIndex } @@ -116,7 +116,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { let factor: CGFloat switch configuration.granularity { case .automatic: - factor = min(5, 1 / (stroke.thickness * 10 / 500)) + factor = min(5, 1 / (stroke.thickness * 1 / 50)) case .fixed: factor = 1 / (stroke.thickness * stroke.penStyle.anyPenStyle.stepRate) case .none: @@ -145,15 +145,28 @@ struct SolidPointStrokeGenerator: StrokeGenerator { } } - private func discardVertices(upto index: Int, on stroke: Stroke) { + private func discardVertices(upto index: Int, quadIndex: Int, on stroke: Stroke) { if index < 0 { stroke.vertices.removeAll() - stroke._quads.removeAll() + discardQuads(from: quadIndex + 1, on: stroke) } else { let count = stroke.vertices.endIndex let dropCount = count - (max(0, index) + 1) stroke.vertices.removeLast(dropCount) - stroke._quads.removeLast(dropCount / 6) + discardQuads(from: quadIndex + 1, on: stroke) + } + } + + private func discardQuads(from start: Int, on stroke: Stroke) { + let quads = stroke.quads.array + Persistence.performe { context in + for index in start.. + @NSManaged var quads: NSMutableOrderedSet @NSManaged var graphicContext: GraphicContext? var angle: CGFloat = 0 @@ -31,7 +31,6 @@ final class Stroke: NSManagedObject { var thicknessFactor: CGFloat = 0.7 var vertices: [QuadVertex] = [] - var _quads: [Quad] = [] var vertexBuffer: MTLBuffer? var vertexCount: Int = 0 @@ -59,14 +58,21 @@ final class Stroke: NSManagedObject { } func loadVertices() { - vertices = strokeQuads - .flatMap { $0.quad.generateVertices() } + vertices = quads + .flatMap { ($0 as? Quad)?.generateVertices(color) ?? [] } vertexCount = vertices.endIndex } - func saveQuads() { - strokeQuads = _quads.map(StrokeQuad.init) - _quads.removeAll() + func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape) -> Quad { + let quad = Quad(context: Persistence.context) + quad.id = UUID() + quad.origin = point + quad.rotation = rotation + quad.size = thickness + quad.shape = shape.rawValue + quads.add(quad) + quad.stroke = self + return quad } } @@ -79,6 +85,9 @@ extension Stroke: Drawable { } func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) { + if isEmpty { + loadVertices() + } guard !isEmpty else { return } prepare(device: device) renderEncoder.setFragmentTexture(texture, index: 0) diff --git a/Memola/Canvas/History/History.swift b/Memola/Canvas/History/History.swift index 87f6911..1fe8651 100644 --- a/Memola/Canvas/History/History.swift +++ b/Memola/Canvas/History/History.swift @@ -49,6 +49,15 @@ class History: ObservableObject { func resetRedo() { redoCache = redoStack + for event in redoStack { + switch event { + case .stroke(let stroke): + Persistence.performe { context in + context.delete(stroke) + } + } + } + Persistence.saveIfNeeded() redoStack.removeAll() } diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 117d49b..c4df853 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -61,7 +61,10 @@ class CanvasViewController: UIViewController { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) - Persistence.shared.viewContext.refresh(canvas, mergeChanges: false) + history.resetRedo() + Persistence.performe { context in + context.refresh(canvas, mergeChanges: false) + } } } diff --git a/Memola/Persistence/Core/Persistence.swift b/Memola/Persistence/Core/Persistence.swift index 505a41f..a488b68 100644 --- a/Memola/Persistence/Core/Persistence.swift +++ b/Memola/Persistence/Core/Persistence.swift @@ -13,11 +13,13 @@ class Persistence { static let shared: Persistence = Persistence() - private init() { - QuadValueTransformer.register() - } + private init() { } - lazy var viewContext: NSManagedObjectContext = { + static var context: NSManagedObjectContext = { + shared.persistentContainer.viewContext + }() + + private lazy var viewContext: NSManagedObjectContext = { persistentContainer.viewContext }() @@ -65,4 +67,18 @@ class Persistence { fatalError("[Memola]: \(error.localizedDescription)") } }() + + static func performe(_ action: (NSManagedObjectContext) -> Void) { + action(shared.viewContext) + } + + static func saveIfNeeded() { + if context.hasChanges { + do { + try context.save() + } catch { + NSLog("[Memola] - \(error.localizedDescription)") + } + } + } } diff --git a/Memola/Persistence/Transformers/QuadValueTransformer.swift b/Memola/Persistence/Transformers/QuadValueTransformer.swift deleted file mode 100644 index 63b03b3..0000000 --- a/Memola/Persistence/Transformers/QuadValueTransformer.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// QuadValueTransformer.swift -// Memola -// -// Created by Dscyre Scotti on 5/8/24. -// - -import CoreData -import Foundation - -@objc(QuadValueTransformer) -class QuadValueTransformer: ValueTransformer { - static let name = NSValueTransformerName(rawValue: String(describing: QuadValueTransformer.self)) - - override class func transformedValueClass() -> AnyClass { - StrokeQuad.self - } - - override func transformedValue(_ value: Any?) -> Any? { - guard let quads = value as? [StrokeQuad] else { - assertionFailure("[Memola] - Failed to transform `[Quad]` to `Data`") - return nil - } - do { - let data = try JSONEncoder().encode(quads.map(\.quad)) - return data - } catch { - print(error.localizedDescription) - assertionFailure("[Memola] - Failed to transform `Quad` to `Data`") - return nil - } - } - - override func reverseTransformedValue(_ value: Any?) -> Any? { - guard let data = value as? Data else { - assertionFailure("[Memola] - Failed to transform `Data` to `Quad`") - return nil - } - do { - let quads = try JSONDecoder().decode([Quad].self, from: data) - return quads.map(StrokeQuad.init) - } catch { - print(error.localizedDescription) - assertionFailure("[Memola] - Failed to transform `Data` to `Quad`") - return nil - } - } - - static func register() { - let transformer = QuadValueTransformer() - ValueTransformer.setValueTransformer(transformer, forName: name) - } -} diff --git a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents index 89c2561..2d521cc 100644 --- a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents +++ b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents @@ -19,13 +19,22 @@ + + + + + + + + + - + \ No newline at end of file