diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index cef513d..9ba2c66 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -142,6 +142,22 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + EC1437B42BE748E60022C903 /* Views */ = { + isa = PBXGroup; + children = ( + ECA738AC2BE60CC600A4542E /* DrawingView.swift */, + ); + path = Views; + sourceTree = ""; + }; + EC1437B52BE748EF0022C903 /* ViewController */ = { + isa = PBXGroup; + children = ( + ECA738AF2BE60D0B00A4542E /* CanvasViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; EC7F6BDF2BE5E6E300A34A7B = { isa = PBXGroup; children = ( @@ -338,8 +354,8 @@ ECA738AE2BE60CEC00A4542E /* Bridge */ = { isa = PBXGroup; children = ( - ECA738AC2BE60CC600A4542E /* DrawingView.swift */, - ECA738AF2BE60D0B00A4542E /* CanvasViewController.swift */, + EC1437B52BE748EF0022C903 /* ViewController */, + EC1437B42BE748E60022C903 /* Views */, ); path = Bridge; sourceTree = ""; diff --git a/Memola/Canvas/Contexts/GraphicContext.swift b/Memola/Canvas/Contexts/GraphicContext.swift index 6c8a24d..39d5e24 100644 --- a/Memola/Canvas/Contexts/GraphicContext.swift +++ b/Memola/Canvas/Contexts/GraphicContext.swift @@ -118,6 +118,14 @@ extension GraphicContext { self.currentPoint = nil delegate?.didUpdate.send() } + + func cancelStroke() { + if !strokes.isEmpty { + strokes.removeLast() + } + currentStroke = nil + currentPoint = nil + } } extension GraphicContext { diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index baac125..f8d2381 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -12,7 +12,7 @@ import Foundation final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicContextDelegate { let size: CGSize - let maximumZoomScale: CGFloat = 30 + let maximumZoomScale: CGFloat = 25 let minimumZoomScale: CGFloat = 3.1 var transform: simd_float4x4 = .init() @@ -32,7 +32,14 @@ final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicCo @Published var state: State = .initial lazy var didUpdate = PassthroughSubject() - init(size: CGSize = .init(width: 8_000, height: 8_000)) { + var hasValidStroke: Bool { + if let currentStroke = graphicContext.currentStroke { + return Date.now.timeIntervalSince(currentStroke.createdAt) * 1000 > 80 + } + return false + } + + init(size: CGSize = .init(width: 4_000, height: 4_000)) { self.size = size } @@ -132,6 +139,10 @@ extension Canvas { graphicContext.endStroke(at: point) } + func cancelTouch() { + graphicContext.cancelStroke() + } + func setGraphicRenderType(_ renderType: GraphicContext.RenderType) { graphicContext.renderType = renderType } diff --git a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift index 5f0add9..7a06d70 100644 --- a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift +++ b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift @@ -89,8 +89,12 @@ struct SolidPointStrokeGenerator: StrokeGenerator { let averageX = (prev.x + current.x) / 2 let averageY = (prev.y + current.y) / 2 let point = CGPoint(x: averageX, y: averageY) - stroke.keyPoints[index] = point - stroke.keyPoints[index - 1] = point + if index != 0 { + stroke.keyPoints[index] = point + } + if index - 1 != 0 { + stroke.keyPoints[index - 1] = point + } } private func addPoint(_ point: CGPoint, on stroke: Stroke) { @@ -117,9 +121,9 @@ struct SolidPointStrokeGenerator: StrokeGenerator { case .none: factor = 1 / (stroke.thickness * 10 / 500) } - let segements = max(Int(distance * factor), 1) - for i in 0...stride * vertexCount) bytes") } } diff --git a/Memola/Canvas/History/History.swift b/Memola/Canvas/History/History.swift index 1cb2279..87f6911 100644 --- a/Memola/Canvas/History/History.swift +++ b/Memola/Canvas/History/History.swift @@ -12,6 +12,8 @@ class History: ObservableObject { @Published var undoStack: [HistoryEvent] = [] @Published var redoStack: [HistoryEvent] = [] + var redoCache: [HistoryEvent] = [] + let historyPublisher = PassthroughSubject() var undoDisabled: Bool { @@ -46,6 +48,15 @@ class History: ObservableObject { } func resetRedo() { + redoCache = redoStack redoStack.removeAll() } + + func restoreUndo() { + if !undoStack.isEmpty { + undoStack.removeLast() + } + redoStack = redoCache + redoCache.removeAll() + } } diff --git a/Memola/Canvas/View/Bridge/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift similarity index 88% rename from Memola/Canvas/View/Bridge/CanvasViewController.swift rename to Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 174ac95..5a3907a 100644 --- a/Memola/Canvas/View/Bridge/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -40,7 +40,6 @@ class CanvasViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() configureViews() - configureGestures() configureListeners() loadMemo() @@ -185,41 +184,6 @@ extension CanvasViewController: MTKViewDelegate { } } -extension CanvasViewController { - func configureGestures() { - let drawingPanGesture = UIPanGestureRecognizer(target: self, action: #selector(recognizePanGesture)) - drawingPanGesture.maximumNumberOfTouches = 1 - drawingPanGesture.minimumNumberOfTouches = 1 - drawingView.addGestureRecognizer(drawingPanGesture) - - let drawingTapGesture = UITapGestureRecognizer(target: self, action: #selector(recognizeTapGesture)) - drawingTapGesture.numberOfTapsRequired = 1 - drawingView.addGestureRecognizer(drawingTapGesture) - } - - @objc func recognizePanGesture(_ gesture: UIPanGestureRecognizer) { - let point = gesture.location(in: drawingView) - switch gesture.state { - case .began: - drawingView.touchBegan(on: point) - case .changed: - drawingView.touchMoved(to: point) - case .ended: - drawingView.touchEnded(to: point) - case .cancelled: - drawingView.touchEnded(to: point) - default: - break - } - } - - @objc func recognizeTapGesture(_ gesture: UITapGestureRecognizer) { - let point = gesture.location(in: drawingView) - drawingView.touchBegan(on: point) - drawingView.touchEnded(to: point) - } -} - extension CanvasViewController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { drawingView @@ -269,6 +233,7 @@ extension CanvasViewController: UIScrollViewDelegate { extension CanvasViewController { func magnificationStarted() { guard !renderer.updatesViewPort else { return } + drawingView.touchCancelled() canvas.updateClipBounds(scrollView, on: drawingView) drawingView.disableUserInteraction() renderer.updatesViewPort = true diff --git a/Memola/Canvas/View/Bridge/DrawingView.swift b/Memola/Canvas/View/Bridge/Views/DrawingView.swift similarity index 51% rename from Memola/Canvas/View/Bridge/DrawingView.swift rename to Memola/Canvas/View/Bridge/Views/DrawingView.swift index 8791cf5..20c263d 100644 --- a/Memola/Canvas/View/Bridge/DrawingView.swift +++ b/Memola/Canvas/View/Bridge/Views/DrawingView.swift @@ -35,11 +35,42 @@ class DrawingView: UIView { renderView.drawableSize = size.multiply(by: 2.5) } - func touchBegan(on point: CGPoint) { + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + !canvas.hasValidStroke + } + + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + guard let touch = touches.first else { return } + let point = touch.location(in: self) + touchBegan(at: point) + } + + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) + guard let touch = touches.first else { return } + let point = touch.location(in: self) + touchMoved(to: point) + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + guard let touch = touches.first else { return } + let point = touch.location(in: self) + touchEnded(at: point) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + guard let touch = touches.first else { return } + let point = touch.location(in: self) + touchEnded(at: point) + } + + func touchBegan(at point: CGPoint) { guard !disablesUserInteraction else { return } guard let pen = tool.selectedPen else { return } let stroke = canvas.beginTouch(at: point.muliply(by: ratio), pen: pen) - renderView.draw() history.addUndo(.stroke(stroke)) history.resetRedo() } @@ -47,15 +78,25 @@ class DrawingView: UIView { func touchMoved(to point: CGPoint) { guard !disablesUserInteraction else { return } canvas.moveTouch(to: point.muliply(by: ratio)) - renderView.draw() + if canvas.hasValidStroke { + renderView.draw() + } } - func touchEnded(to point: CGPoint) { + func touchEnded(at point: CGPoint) { guard !disablesUserInteraction else { return } canvas.endTouch(at: point.muliply(by: ratio)) renderView.draw() } + func touchCancelled() { + if canvas.graphicContext.currentStroke != nil { + canvas.cancelTouch() + renderView.draw() + history.restoreUndo() + } + } + func disableUserInteraction() { disablesUserInteraction = true }