diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index 56a8ff1..34198cf 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -60,18 +60,32 @@ final class GraphicContext: @unchecked Sendable { } extension GraphicContext { - func loadStrokes() { + func loadStrokes(_ bounds: CGRect) { guard let object else { return } self.strokes = object.strokes.compactMap { stroke -> Stroke? in guard let stroke = stroke as? StrokeObject else { return nil } let _stroke = Stroke(object: stroke) - _stroke.loadQuads() - withPersistence(\.backgroundContext) { [stroke] context in - context.refresh(stroke, mergeChanges: false) + if _stroke.isVisible(in: bounds) { + _stroke.loadQuads() + withPersistence(\.backgroundContext) { [stroke] context in + context.refresh(stroke, mergeChanges: false) + } + } else { + withPersistence(\.backgroundContext) { [stroke] context in + _stroke.loadQuads() + context.refresh(stroke, mergeChanges: false) + } } return _stroke } } + + func loadQuads(_ bounds: CGRect) { + for stroke in self.strokes { + guard stroke.isVisible(in: bounds), stroke.quads.isEmpty else { continue } + stroke.loadQuads() + } + } } extension GraphicContext: Drawable { @@ -93,6 +107,7 @@ extension GraphicContext: Drawable { extension GraphicContext { func beginStroke(at point: CGPoint, pen: Pen) -> Stroke { let stroke = Stroke( + bounds: [point.x - pen.thickness, point.y - pen.thickness, point.x + pen.thickness, point.y + pen.thickness], color: pen.color, style: pen.strokeStyle.rawValue, createdAt: .now, @@ -100,6 +115,7 @@ extension GraphicContext { ) withPersistence(\.backgroundContext) { [graphicContext = object, _stroke = stroke] context in let stroke = StrokeObject(\.backgroundContext) + stroke.bounds = _stroke.bounds stroke.color = _stroke.color stroke.style = _stroke.style stroke.thickness = _stroke.thickness @@ -126,10 +142,11 @@ extension GraphicContext { func endStroke(at point: CGPoint) { guard currentPoint != nil, let currentStroke else { return } currentStroke.finish(at: point) - let saveIndex = currentStroke.batchIndex - let quads = Array(currentStroke.quads[saveIndex.. Bool { + bounds.contains(strokeBounds) + } func begin(at point: CGPoint) { penStyle.anyPenStyle.generator.begin(at: point, on: self) @@ -106,6 +121,8 @@ extension Stroke { } func saveQuads(for quads: [Quad]) { + var topLeft: CGPoint = CGPoint(x: bounds[0], y: bounds[1]) + var bottomRight: CGPoint = CGPoint(x: bounds[2], y: bounds[3]) for _quad in quads { let quad = QuadObject(\.backgroundContext) quad.originX = _quad.originX.cgFloat @@ -116,7 +133,12 @@ extension Stroke { quad.color = _quad.getColor() quad.stroke = object object?.quads.add(quad) + topLeft.x = min(quad.originX, topLeft.x) + topLeft.y = min(quad.originY, topLeft.y) + bottomRight.x = max(quad.originX, bottomRight.x) + bottomRight.y = max(quad.originY, bottomRight.y) } + bounds = [topLeft.x, topLeft.y, bottomRight.x, bottomRight.y] } } diff --git a/Memola/Canvas/RenderPasses/GraphicRenderPass.swift b/Memola/Canvas/RenderPasses/GraphicRenderPass.swift index fd685de..0f8e4e5 100644 --- a/Memola/Canvas/RenderPasses/GraphicRenderPass.swift +++ b/Memola/Canvas/RenderPasses/GraphicRenderPass.swift @@ -49,6 +49,7 @@ class GraphicRenderPass: RenderPass { 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 if stroke.isEraserPenStyle { diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 69faa94..0a37e68 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -41,13 +41,13 @@ class CanvasViewController: UIViewController { super.viewDidLoad() configureViews() configureListeners() - - loadMemo() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) resizeDocumentView() + updateDocumentBounds() + loadMemo() } override func viewDidLayoutSubviews() { @@ -119,11 +119,11 @@ extension CanvasViewController { let newFrame = CGRect(x: 0, y: 0, width: width, height: height) drawingView.frame = newFrame - scrollView.setZoomScale(canvas.minimumZoomScale, animated: true) + scrollView.setZoomScale(canvas.defaultZoomScale, animated: true) centerDocumentView(to: newSize) - let offsetX = (newFrame.width * canvas.minimumZoomScale - view.frame.width) / 2 - let offsetY = (newFrame.height * canvas.minimumZoomScale - view.frame.height) / 2 + let offsetX = (newFrame.width * canvas.defaultZoomScale - view.frame.width) / 2 + let offsetY = (newFrame.height * canvas.defaultZoomScale - view.frame.height) / 2 let point = CGPoint(x: offsetX, y: offsetY) scrollView.setContentOffset(point, animated: true) @@ -138,6 +138,20 @@ extension CanvasViewController { let horizontalPadding = documentViewSize.width < scrollViewSize.width ? (scrollViewSize.width - documentViewSize.width) / 2 : 0 self.scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) } + + func updateDocumentBounds() { + var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale) + let xDelta = bounds.minX * 0.05 + let yDelta = bounds.minY * 0.05 + bounds.origin.x -= xDelta + bounds.origin.y -= yDelta + bounds.size.width += xDelta * 2 + bounds.size.height += yDelta * 2 + canvas.bounds = bounds + if canvas.state == .loaded { + canvas.loadStrokes(bounds) + } + } } extension CanvasViewController { @@ -208,6 +222,7 @@ extension CanvasViewController: UIScrollViewDelegate { } func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { + updateDocumentBounds() centerDocumentView() magnificationEnded() } @@ -234,6 +249,7 @@ extension CanvasViewController: UIScrollViewDelegate { } func scrollViewDidEndScrolling(_ scrollView: UIScrollView) { + updateDocumentBounds() draggingEnded() } } diff --git a/Memola/Extensions/CGRect++.swift b/Memola/Extensions/CGRect++.swift index a576e31..ca95178 100644 --- a/Memola/Extensions/CGRect++.swift +++ b/Memola/Extensions/CGRect++.swift @@ -16,4 +16,8 @@ extension CGRect { t = t.scaledBy(x: rect.width, y: rect.height) return t } + + func muliply(by factor: CGFloat) -> CGRect { + CGRect(origin: origin.muliply(by: factor), size: size.multiply(by: factor)) + } } diff --git a/Memola/Persistence/Objects/StrokeObject.swift b/Memola/Persistence/Objects/StrokeObject.swift index ffa049b..2d9767a 100644 --- a/Memola/Persistence/Objects/StrokeObject.swift +++ b/Memola/Persistence/Objects/StrokeObject.swift @@ -10,6 +10,7 @@ import Foundation @objc(StrokeObject) final class StrokeObject: NSManagedObject { + @NSManaged var bounds: [CGFloat] @NSManaged var color: [CGFloat] @NSManaged var style: Int16 @NSManaged var createdAt: Date diff --git a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents index 72e5132..1b277fa 100644 --- a/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents +++ b/Memola/Resources/Models/MemolaModel.xcdatamodeld/MemolaModel.xcdatamodel/contents @@ -26,6 +26,7 @@ +