diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 83a5b4b..7de7d5d 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -82,6 +82,8 @@ ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; }; ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; }; ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; }; + ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; }; + ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; }; ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BC2C00AA170045C53D /* EraserStroke.swift */; }; ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BE2C00AB440045C53D /* Stroke.swift */; }; ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */; }; @@ -172,6 +174,8 @@ ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = ""; }; ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = ""; }; + ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = ""; }; + ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = ""; }; ECE883BC2C00AA170045C53D /* EraserStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserStroke.swift; sourceTree = ""; }; ECE883BE2C00AB440045C53D /* Stroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = ""; }; ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeStyle.swift; sourceTree = ""; }; @@ -382,8 +386,8 @@ ECA7387E2BE5FE4200A4542E /* Canvas */ = { isa = PBXGroup; children = ( + ECD12A872C19EF8700B96E12 /* Elements */, EC2BEBF22C0F5FE1005DB0AF /* RTree */, - ECA738F92BE6130000A4542E /* Geometries */, ECA738812BE5FEEE00A4542E /* Abstracts */, ECA738992BE6018900A4542E /* Buffers */, ECA738C72BE60EE200A4542E /* Contexts */, @@ -645,6 +649,23 @@ path = Core; sourceTree = ""; }; + ECD12A872C19EF8700B96E12 /* Elements */ = { + isa = PBXGroup; + children = ( + ECD12A882C19EF9500B96E12 /* Core */, + ECA738F92BE6130000A4542E /* Geometries */, + ); + path = Elements; + sourceTree = ""; + }; + ECD12A882C19EF9500B96E12 /* Core */ = { + isa = PBXGroup; + children = ( + ECD12A892C19EFB000B96E12 /* Element.swift */, + ); + path = Core; + sourceTree = ""; + }; ECE883B82C009DC30045C53D /* Strokes */ = { isa = PBXGroup; children = ( @@ -676,6 +697,7 @@ EC0D14202BF79C73009BFE5F /* ToolObject.swift */, EC0D14252BF7A8C9009BFE5F /* PenObject.swift */, EC9AB09E2C1401A40076AF58 /* EraserObject.swift */, + ECD12A852C19EE3900B96E12 /* ElementObject.swift */, ); path = Objects; sourceTree = ""; @@ -805,6 +827,7 @@ ECA738B82BE60DDC00A4542E /* HistoryEvent.swift in Sources */, EC0D14262BF7A8C9009BFE5F /* PenObject.swift in Sources */, ECA738952BE6012D00A4542E /* ViewPort.metal in Sources */, + ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */, EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */, ECA738F02BE6127700A4542E /* CGSize++.swift in Sources */, ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */, @@ -840,6 +863,7 @@ EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */, EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */, ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */, + ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */, EC0D14282BF7BF20009BFE5F /* ContextMenuViewModifier.swift in Sources */, EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */, ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */, diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index 37c637c..a370df3 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -11,7 +11,7 @@ import CoreData import Foundation final class GraphicContext: @unchecked Sendable { - var tree: RTree = RTree(maxEntries: 8) + var tree: RTree = RTree(maxEntries: 8) var eraserStrokes: Set = [] var object: GraphicContextObject? @@ -48,9 +48,9 @@ final class GraphicContext: @unchecked Sendable { switch stroke.style { case .marker: guard let penStroke = stroke.stroke(as: PenStroke.self) else { return } - tree.remove(penStroke.anyStroke, in: penStroke.strokeBox) + tree.remove(penStroke.element, in: penStroke.strokeBox) withPersistence(\.backgroundContext) { [weak penStroke] context in - penStroke?.object?.graphicContext = nil + penStroke?.object?.element?.graphicContext = nil try context.saveIfNeeded() context.refreshAllObjects() } @@ -81,9 +81,9 @@ final class GraphicContext: @unchecked Sendable { guard let penStroke = stroke.stroke(as: PenStroke.self) else { break } - tree.insert(penStroke.anyStroke, in: penStroke.strokeBox) + tree.insert(penStroke.element, in: penStroke.strokeBox) withPersistence(\.backgroundContext) { [weak self, weak penStroke] context in - penStroke?.object?.graphicContext = self?.object + penStroke?.object?.element?.graphicContext = self?.object try context.saveIfNeeded() context.refreshAllObjects() } @@ -114,32 +114,43 @@ extension GraphicContext { guard let object else { return } let queue = OperationQueue() queue.qualityOfService = .userInteractive - object.strokes.forEach { stroke in - guard let stroke = stroke as? StrokeObject, stroke.style == 0 else { return } - let _stroke = PenStroke(object: stroke) - tree.insert(_stroke.anyStroke, in: _stroke.strokeBox) - if _stroke.isVisible(in: bounds) { - let id = stroke.objectID - queue.addOperation { [weak self] in - guard let self else { return } - withPersistenceSync(\.newBackgroundContext) { [weak _stroke] context in - guard let stroke = try? context.existingObject(with: id) as? StrokeObject else { return } - _stroke?.loadQuads(from: stroke, with: self) + object.elements.forEach { element in + guard let element = element as? ElementObject else { return } + switch element.type { + case 0: + guard let stroke = element.stroke, stroke.style == 0 else { return } + let _stroke = PenStroke(object: stroke) + tree.insert(_stroke.element, in: _stroke.strokeBox) + if _stroke.isVisible(in: bounds) { + let id = stroke.objectID + queue.addOperation { [weak self] in + guard let self else { return } + withPersistenceSync(\.newBackgroundContext) { [weak _stroke] context in + guard let stroke = try? context.existingObject(with: id) as? StrokeObject else { return } + _stroke?.loadQuads(from: stroke, with: self) + context.refreshAllObjects() + } + } + } else { + withPersistence(\.backgroundContext) { [weak self, weak _stroke] context in + guard let self else { return } + _stroke?.loadQuads(with: self) context.refreshAllObjects() } } - } else { - withPersistence(\.backgroundContext) { [weak self, weak _stroke] context in - guard let self else { return } - _stroke?.loadQuads(with: self) - context.refreshAllObjects() - } + case 1: + #warning("TODO: implement photo") + break + default: + break } + } queue.waitUntilAllOperationsAreFinished() } func loadQuads(_ bounds: CGRect, on context: NSManagedObjectContext) { + #warning("TODO: implement photo") for _stroke in self.tree.search(box: bounds.box) { guard let stroke = _stroke.stroke(as: PenStroke.self), stroke.isEmpty else { continue } stroke.loadQuads(with: self) @@ -185,8 +196,13 @@ extension GraphicContext { stroke.createdAt = _stroke.createdAt stroke.quads = [] stroke.erasers = .init() - stroke.graphicContext = graphicContext - graphicContext?.strokes.add(stroke) + let element = ElementObject(\.backgroundContext) + element.createdAt = _stroke.createdAt + element.type = 0 + element.graphicContext = graphicContext + stroke.element = element + element.stroke = stroke + graphicContext?.elements.add(element) _stroke.object = stroke try context.saveIfNeeded() } @@ -235,7 +251,7 @@ extension GraphicContext { currentStroke.finish(at: point) if let penStroke = currentStroke.stroke(as: PenStroke.self) { penStroke.saveQuads() - tree.insert(currentStroke.anyStroke, in: currentStroke.strokeBox) + tree.insert(currentStroke.element, in: currentStroke.strokeBox) withPersistence(\.backgroundContext) { [weak penStroke] context in guard let penStroke else { return } penStroke.object?.bounds = penStroke.bounds @@ -264,9 +280,8 @@ extension GraphicContext { guard let _stroke = stroke.stroke(as: PenStroke.self) else { break } withPersistence(\.backgroundContext) { [weak graphicContext = object, weak _stroke] context in guard let _stroke else { return } - if let stroke = _stroke.object { - graphicContext?.strokes.remove(stroke) - context.delete(stroke) + if let element = _stroke.object?.element { + graphicContext?.elements.remove(element) } try context.saveIfNeeded() } diff --git a/Memola/Canvas/Elements/Core/Element.swift b/Memola/Canvas/Elements/Core/Element.swift new file mode 100644 index 0000000..a59da0b --- /dev/null +++ b/Memola/Canvas/Elements/Core/Element.swift @@ -0,0 +1,20 @@ +// +// Element.swift +// Memola +// +// Created by Dscyre Scotti on 6/12/24. +// + +import Foundation + +enum Element: Equatable, Comparable { + case stroke(AnyStroke) + case photo + + func stroke(as type: S.Type) -> S? { + guard case let .stroke(anyStroke) = self else { + return nil + } + return anyStroke.stroke(as: type) + } +} diff --git a/Memola/Canvas/Geometries/Primitives/Quad.swift b/Memola/Canvas/Elements/Geometries/Primitives/Quad.swift similarity index 100% rename from Memola/Canvas/Geometries/Primitives/Quad.swift rename to Memola/Canvas/Elements/Geometries/Primitives/Quad.swift diff --git a/Memola/Canvas/Geometries/Primitives/QuadShape.swift b/Memola/Canvas/Elements/Geometries/Primitives/QuadShape.swift similarity index 100% rename from Memola/Canvas/Geometries/Primitives/QuadShape.swift rename to Memola/Canvas/Elements/Geometries/Primitives/QuadShape.swift diff --git a/Memola/Canvas/Geometries/Stroke/Algorithms/MovingAverage.swift b/Memola/Canvas/Elements/Geometries/Stroke/Algorithms/MovingAverage.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Algorithms/MovingAverage.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Algorithms/MovingAverage.swift diff --git a/Memola/Canvas/Geometries/Stroke/Core/AnyStroke.swift b/Memola/Canvas/Elements/Geometries/Stroke/Core/AnyStroke.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Core/AnyStroke.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Core/AnyStroke.swift diff --git a/Memola/Canvas/Geometries/Stroke/Core/Stroke.swift b/Memola/Canvas/Elements/Geometries/Stroke/Core/Stroke.swift similarity index 98% rename from Memola/Canvas/Geometries/Stroke/Core/Stroke.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Core/Stroke.swift index 6eb7526..f00857c 100644 --- a/Memola/Canvas/Geometries/Stroke/Core/Stroke.swift +++ b/Memola/Canvas/Elements/Geometries/Stroke/Core/Stroke.swift @@ -125,4 +125,8 @@ extension Stroke { var anyStroke: AnyStroke { AnyStroke(self) } + + var element: Element { + .stroke(anyStroke) + } } diff --git a/Memola/Canvas/Geometries/Stroke/Core/StrokeGenerator.swift b/Memola/Canvas/Elements/Geometries/Stroke/Core/StrokeGenerator.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Core/StrokeGenerator.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Core/StrokeGenerator.swift diff --git a/Memola/Canvas/Geometries/Stroke/Core/StrokeStyle.swift b/Memola/Canvas/Elements/Geometries/Stroke/Core/StrokeStyle.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Core/StrokeStyle.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Core/StrokeStyle.swift diff --git a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift b/Memola/Canvas/Elements/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift diff --git a/Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift b/Memola/Canvas/Elements/Geometries/Stroke/Strokes/EraserStroke.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Strokes/EraserStroke.swift diff --git a/Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift b/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift similarity index 100% rename from Memola/Canvas/Geometries/Stroke/Strokes/PenStroke.swift rename to Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift diff --git a/Memola/Canvas/RenderPasses/GraphicRenderPass.swift b/Memola/Canvas/RenderPasses/GraphicRenderPass.swift index aca8d71..80dd01d 100644 --- a/Memola/Canvas/RenderPasses/GraphicRenderPass.swift +++ b/Memola/Canvas/RenderPasses/GraphicRenderPass.swift @@ -45,32 +45,37 @@ class GraphicRenderPass: RenderPass { let graphicContext = canvas.graphicContext if renderer.redrawsGraphicRender { canvas.setGraphicRenderType(.finished) - for _stroke in graphicContext.tree.search(box: canvas.bounds.box) { - let stroke = _stroke.value - if graphicContext.previousStroke === stroke || graphicContext.currentStroke === stroke { - continue - } - guard stroke.isVisible(in: canvas.bounds) else { continue } - descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load - clearsTexture = false - switch stroke.style { - case .eraser: - eraserRenderPass.stroke = stroke - eraserRenderPass.descriptor = descriptor - eraserRenderPass.draw(on: canvas, with: renderer) - case .marker: - canvas.setGraphicRenderType(.finished) - strokeRenderPass.stroke = stroke - strokeRenderPass.graphicDescriptor = descriptor - strokeRenderPass.graphicPipelineState = graphicPipelineState - strokeRenderPass.draw(on: canvas, with: renderer) - - if let stroke = stroke as? PenStroke, !stroke.isEmptyErasedQuads { - descriptor.colorAttachments[0].loadAction = .load + for _element in graphicContext.tree.search(box: canvas.bounds.box) { + switch _element { + case .stroke(let _stroke): + let stroke = _stroke.value + if graphicContext.previousStroke === stroke || graphicContext.currentStroke === stroke { + continue + } + guard stroke.isVisible(in: canvas.bounds) else { continue } + descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load + clearsTexture = false + switch stroke.style { + case .eraser: eraserRenderPass.stroke = stroke eraserRenderPass.descriptor = descriptor eraserRenderPass.draw(on: canvas, with: renderer) + case .marker: + canvas.setGraphicRenderType(.finished) + strokeRenderPass.stroke = stroke + strokeRenderPass.graphicDescriptor = descriptor + strokeRenderPass.graphicPipelineState = graphicPipelineState + strokeRenderPass.draw(on: canvas, with: renderer) + + if let stroke = stroke as? PenStroke, !stroke.isEmptyErasedQuads { + descriptor.colorAttachments[0].loadAction = .load + eraserRenderPass.stroke = stroke + eraserRenderPass.descriptor = descriptor + eraserRenderPass.draw(on: canvas, with: renderer) + } } + case .photo: + break } } renderer.redrawsGraphicRender = false diff --git a/Memola/Features/Memos/MemosView.swift b/Memola/Features/Memos/MemosView.swift index 99e87c5..1e721da 100644 --- a/Memola/Features/Memos/MemosView.swift +++ b/Memola/Features/Memos/MemosView.swift @@ -85,7 +85,7 @@ struct MemosView: View { } let graphicContextObject = GraphicContextObject(\.viewContext) - graphicContextObject.strokes = [] + graphicContextObject.elements = [] memoObject.canvas = canvasObject memoObject.tool = toolObject diff --git a/Memola/Persistence/Objects/CanvasObject.swift b/Memola/Persistence/Objects/CanvasObject.swift index 6204db1..66fc646 100644 --- a/Memola/Persistence/Objects/CanvasObject.swift +++ b/Memola/Persistence/Objects/CanvasObject.swift @@ -8,7 +8,6 @@ import CoreData import Foundation - @objc(CanvasObject) final class CanvasObject: NSManagedObject { @NSManaged var width: CGFloat diff --git a/Memola/Persistence/Objects/ElementObject.swift b/Memola/Persistence/Objects/ElementObject.swift new file mode 100644 index 0000000..83009c8 --- /dev/null +++ b/Memola/Persistence/Objects/ElementObject.swift @@ -0,0 +1,17 @@ +// +// ElementObject.swift +// Memola +// +// Created by Dscyre Scotti on 6/12/24. +// + +import CoreData +import Foundation + +@objc(ElementObject) +final class ElementObject: NSManagedObject { + @NSManaged var type: Int16 + @NSManaged var createdAt: Date? + @NSManaged var stroke: StrokeObject? + @NSManaged var graphicContext: GraphicContextObject? +} diff --git a/Memola/Persistence/Objects/GraphicContextObject.swift b/Memola/Persistence/Objects/GraphicContextObject.swift index f62a765..ea26216 100644 --- a/Memola/Persistence/Objects/GraphicContextObject.swift +++ b/Memola/Persistence/Objects/GraphicContextObject.swift @@ -11,5 +11,5 @@ import Foundation @objc(GraphicContextObject) final class GraphicContextObject: NSManagedObject { @NSManaged var canvas: CanvasObject? - @NSManaged var strokes: NSMutableOrderedSet + @NSManaged var elements: NSMutableOrderedSet } diff --git a/Memola/Persistence/Objects/StrokeObject.swift b/Memola/Persistence/Objects/StrokeObject.swift index e304f46..0307920 100644 --- a/Memola/Persistence/Objects/StrokeObject.swift +++ b/Memola/Persistence/Objects/StrokeObject.swift @@ -17,5 +17,5 @@ final class StrokeObject: NSManagedObject { @NSManaged var thickness: CGFloat @NSManaged var quads: NSMutableOrderedSet @NSManaged var erasers: NSMutableSet - @NSManaged var graphicContext: GraphicContextObject? + @NSManaged var element: ElementObject? } diff --git a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents index c9ca0bf..9b5fada 100644 --- a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents +++ b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents @@ -6,6 +6,12 @@ + + + + + + @@ -17,7 +23,7 @@ - + @@ -50,8 +56,8 @@ + -