mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-05-16 20:57:15 +02:00
Merge pull request #17 from dscyrescotti/bug/stroke-enhancement
Resolve conflict between responder touches and scrollview pinch gesture
This commit is contained in:
@@ -142,6 +142,22 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
EC1437B42BE748E60022C903 /* Views */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
ECA738AC2BE60CC600A4542E /* DrawingView.swift */,
|
||||||
|
);
|
||||||
|
path = Views;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC1437B52BE748EF0022C903 /* ViewController */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
ECA738AF2BE60D0B00A4542E /* CanvasViewController.swift */,
|
||||||
|
);
|
||||||
|
path = ViewController;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
EC7F6BDF2BE5E6E300A34A7B = {
|
EC7F6BDF2BE5E6E300A34A7B = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -338,8 +354,8 @@
|
|||||||
ECA738AE2BE60CEC00A4542E /* Bridge */ = {
|
ECA738AE2BE60CEC00A4542E /* Bridge */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECA738AC2BE60CC600A4542E /* DrawingView.swift */,
|
EC1437B52BE748EF0022C903 /* ViewController */,
|
||||||
ECA738AF2BE60D0B00A4542E /* CanvasViewController.swift */,
|
EC1437B42BE748E60022C903 /* Views */,
|
||||||
);
|
);
|
||||||
path = Bridge;
|
path = Bridge;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|||||||
@@ -118,6 +118,14 @@ extension GraphicContext {
|
|||||||
self.currentPoint = nil
|
self.currentPoint = nil
|
||||||
delegate?.didUpdate.send()
|
delegate?.didUpdate.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cancelStroke() {
|
||||||
|
if !strokes.isEmpty {
|
||||||
|
strokes.removeLast()
|
||||||
|
}
|
||||||
|
currentStroke = nil
|
||||||
|
currentPoint = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension GraphicContext {
|
extension GraphicContext {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Foundation
|
|||||||
|
|
||||||
final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicContextDelegate {
|
final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicContextDelegate {
|
||||||
let size: CGSize
|
let size: CGSize
|
||||||
let maximumZoomScale: CGFloat = 30
|
let maximumZoomScale: CGFloat = 25
|
||||||
let minimumZoomScale: CGFloat = 3.1
|
let minimumZoomScale: CGFloat = 3.1
|
||||||
|
|
||||||
var transform: simd_float4x4 = .init()
|
var transform: simd_float4x4 = .init()
|
||||||
@@ -32,7 +32,14 @@ final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicCo
|
|||||||
@Published var state: State = .initial
|
@Published var state: State = .initial
|
||||||
lazy var didUpdate = PassthroughSubject<Void, Never>()
|
lazy var didUpdate = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
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
|
self.size = size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +139,10 @@ extension Canvas {
|
|||||||
graphicContext.endStroke(at: point)
|
graphicContext.endStroke(at: point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cancelTouch() {
|
||||||
|
graphicContext.cancelStroke()
|
||||||
|
}
|
||||||
|
|
||||||
func setGraphicRenderType(_ renderType: GraphicContext.RenderType) {
|
func setGraphicRenderType(_ renderType: GraphicContext.RenderType) {
|
||||||
graphicContext.renderType = renderType
|
graphicContext.renderType = renderType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,8 +89,12 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
let averageX = (prev.x + current.x) / 2
|
let averageX = (prev.x + current.x) / 2
|
||||||
let averageY = (prev.y + current.y) / 2
|
let averageY = (prev.y + current.y) / 2
|
||||||
let point = CGPoint(x: averageX, y: averageY)
|
let point = CGPoint(x: averageX, y: averageY)
|
||||||
stroke.keyPoints[index] = point
|
if index != 0 {
|
||||||
stroke.keyPoints[index - 1] = point
|
stroke.keyPoints[index] = point
|
||||||
|
}
|
||||||
|
if index - 1 != 0 {
|
||||||
|
stroke.keyPoints[index - 1] = point
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addPoint(_ point: CGPoint, on stroke: Stroke) {
|
private func addPoint(_ point: CGPoint, on stroke: Stroke) {
|
||||||
@@ -117,9 +121,9 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
case .none:
|
case .none:
|
||||||
factor = 1 / (stroke.thickness * 10 / 500)
|
factor = 1 / (stroke.thickness * 10 / 500)
|
||||||
}
|
}
|
||||||
let segements = max(Int(distance * factor), 1)
|
let segments = max(Int(distance * factor), 1)
|
||||||
for i in 0..<segements {
|
for i in 0..<segments {
|
||||||
let t = CGFloat(i) / CGFloat(segements)
|
let t = CGFloat(i) / CGFloat(segments)
|
||||||
let x = pow(1 - t, 2) * start.x + 2.0 * (1 - t) * t * control.x + t * t * end.x
|
let x = pow(1 - t, 2) * start.x + 2.0 * (1 - t) * t * control.x + t * t * end.x
|
||||||
let y = pow(1 - t, 2) * start.y + 2.0 * (1 - t) * t * control.y + t * t * end.y
|
let y = pow(1 - t, 2) * start.y + 2.0 * (1 - t) * t * control.y + t * t * end.y
|
||||||
let point = CGPoint(x: x, y: y)
|
let point = CGPoint(x: x, y: y)
|
||||||
@@ -127,6 +131,19 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#warning("TODO: remove later")
|
||||||
|
private func addLine(from start: CGPoint, to end: CGPoint, on stroke: Stroke) {
|
||||||
|
let distance = end.distance(to: start)
|
||||||
|
let segments = max(distance / stroke.style.stepRate, 2)
|
||||||
|
for i in 0..<Int(segments) {
|
||||||
|
let i = CGFloat(i)
|
||||||
|
let x = start.x + (end.x - start.x) * (i / segments)
|
||||||
|
let y = start.y + (end.y - start.y) * (i / segments)
|
||||||
|
let point = CGPoint(x: x, y: y)
|
||||||
|
addPoint(point, on: stroke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func discardPoints(upto index: Int, on stroke: Stroke) {
|
private func discardPoints(upto index: Int, on stroke: Stroke) {
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
stroke.vertices.removeAll()
|
stroke.vertices.removeAll()
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ class Stroke: Codable {
|
|||||||
var vertices: [QuadVertex] = []
|
var vertices: [QuadVertex] = []
|
||||||
var vertexBuffer: MTLBuffer?
|
var vertexBuffer: MTLBuffer?
|
||||||
var vertexCount: Int = 0
|
var vertexCount: Int = 0
|
||||||
var tailVertices: [QuadVertex] = []
|
|
||||||
|
let createdAt: Date = Date()
|
||||||
|
|
||||||
var texture: MTLTexture?
|
var texture: MTLTexture?
|
||||||
|
|
||||||
@@ -90,7 +91,6 @@ class Stroke: Codable {
|
|||||||
|
|
||||||
func finish(at point: CGPoint) {
|
func finish(at point: CGPoint) {
|
||||||
style.generator.finish(at: point, on: self)
|
style.generator.finish(at: point, on: self)
|
||||||
NSLog("[Memola] - \(MemoryLayout<QuadVertex>.stride * vertexCount) bytes")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class History: ObservableObject {
|
|||||||
@Published var undoStack: [HistoryEvent] = []
|
@Published var undoStack: [HistoryEvent] = []
|
||||||
@Published var redoStack: [HistoryEvent] = []
|
@Published var redoStack: [HistoryEvent] = []
|
||||||
|
|
||||||
|
var redoCache: [HistoryEvent] = []
|
||||||
|
|
||||||
let historyPublisher = PassthroughSubject<HistoryAction, Never>()
|
let historyPublisher = PassthroughSubject<HistoryAction, Never>()
|
||||||
|
|
||||||
var undoDisabled: Bool {
|
var undoDisabled: Bool {
|
||||||
@@ -46,6 +48,15 @@ class History: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resetRedo() {
|
func resetRedo() {
|
||||||
|
redoCache = redoStack
|
||||||
redoStack.removeAll()
|
redoStack.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func restoreUndo() {
|
||||||
|
if !undoStack.isEmpty {
|
||||||
|
undoStack.removeLast()
|
||||||
|
}
|
||||||
|
redoStack = redoCache
|
||||||
|
redoCache.removeAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class CanvasViewController: UIViewController {
|
|||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
configureViews()
|
configureViews()
|
||||||
configureGestures()
|
|
||||||
configureListeners()
|
configureListeners()
|
||||||
|
|
||||||
loadMemo()
|
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 {
|
extension CanvasViewController: UIScrollViewDelegate {
|
||||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
drawingView
|
drawingView
|
||||||
@@ -269,6 +233,7 @@ extension CanvasViewController: UIScrollViewDelegate {
|
|||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func magnificationStarted() {
|
func magnificationStarted() {
|
||||||
guard !renderer.updatesViewPort else { return }
|
guard !renderer.updatesViewPort else { return }
|
||||||
|
drawingView.touchCancelled()
|
||||||
canvas.updateClipBounds(scrollView, on: drawingView)
|
canvas.updateClipBounds(scrollView, on: drawingView)
|
||||||
drawingView.disableUserInteraction()
|
drawingView.disableUserInteraction()
|
||||||
renderer.updatesViewPort = true
|
renderer.updatesViewPort = true
|
||||||
@@ -35,11 +35,42 @@ class DrawingView: UIView {
|
|||||||
renderView.drawableSize = size.multiply(by: 2.5)
|
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<UITouch>, 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<UITouch>, 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<UITouch>, 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<UITouch>, 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 !disablesUserInteraction else { return }
|
||||||
guard let pen = tool.selectedPen else { return }
|
guard let pen = tool.selectedPen else { return }
|
||||||
let stroke = canvas.beginTouch(at: point.muliply(by: ratio), pen: pen)
|
let stroke = canvas.beginTouch(at: point.muliply(by: ratio), pen: pen)
|
||||||
renderView.draw()
|
|
||||||
history.addUndo(.stroke(stroke))
|
history.addUndo(.stroke(stroke))
|
||||||
history.resetRedo()
|
history.resetRedo()
|
||||||
}
|
}
|
||||||
@@ -47,15 +78,25 @@ class DrawingView: UIView {
|
|||||||
func touchMoved(to point: CGPoint) {
|
func touchMoved(to point: CGPoint) {
|
||||||
guard !disablesUserInteraction else { return }
|
guard !disablesUserInteraction else { return }
|
||||||
canvas.moveTouch(to: point.muliply(by: ratio))
|
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 }
|
guard !disablesUserInteraction else { return }
|
||||||
canvas.endTouch(at: point.muliply(by: ratio))
|
canvas.endTouch(at: point.muliply(by: ratio))
|
||||||
renderView.draw()
|
renderView.draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func touchCancelled() {
|
||||||
|
if canvas.graphicContext.currentStroke != nil {
|
||||||
|
canvas.cancelTouch()
|
||||||
|
renderView.draw()
|
||||||
|
history.restoreUndo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func disableUserInteraction() {
|
func disableUserInteraction() {
|
||||||
disablesUserInteraction = true
|
disablesUserInteraction = true
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user