mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-03-24 18:31:23 +01:00
bug: sync pen drawing and stroke generating
This commit is contained in:
@@ -142,6 +142,22 @@
|
||||
/* End PBXFrameworksBuildPhase 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 = {
|
||||
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 = "<group>";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,7 @@ final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicCo
|
||||
@Published var state: State = .initial
|
||||
lazy var didUpdate = PassthroughSubject<Void, Never>()
|
||||
|
||||
init(size: CGSize = .init(width: 8_000, height: 8_000)) {
|
||||
init(size: CGSize = .init(width: 4_000, height: 4_000)) {
|
||||
self.size = size
|
||||
}
|
||||
|
||||
@@ -132,6 +132,10 @@ extension Canvas {
|
||||
graphicContext.endStroke(at: point)
|
||||
}
|
||||
|
||||
func cancelTouch() {
|
||||
graphicContext.cancelStroke()
|
||||
}
|
||||
|
||||
func setGraphicRenderType(_ renderType: GraphicContext.RenderType) {
|
||||
graphicContext.renderType = renderType
|
||||
}
|
||||
|
||||
@@ -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..<segements {
|
||||
let t = CGFloat(i) / CGFloat(segements)
|
||||
let segments = max(Int(distance * factor), 1)
|
||||
for i in 0..<segments {
|
||||
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 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)
|
||||
@@ -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) {
|
||||
if index < 0 {
|
||||
stroke.vertices.removeAll()
|
||||
|
||||
@@ -21,7 +21,6 @@ class Stroke: Codable {
|
||||
var vertices: [QuadVertex] = []
|
||||
var vertexBuffer: MTLBuffer?
|
||||
var vertexCount: Int = 0
|
||||
var tailVertices: [QuadVertex] = []
|
||||
|
||||
var texture: MTLTexture?
|
||||
|
||||
@@ -90,7 +89,6 @@ class Stroke: Codable {
|
||||
|
||||
func finish(at point: CGPoint) {
|
||||
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 redoStack: [HistoryEvent] = []
|
||||
|
||||
var redoCache: [HistoryEvent] = []
|
||||
|
||||
let historyPublisher = PassthroughSubject<HistoryAction, Never>()
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// DrawingView.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/4/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MetalKit
|
||||
import Foundation
|
||||
|
||||
class DrawingView: UIView {
|
||||
let tool: Tool
|
||||
let canvas: Canvas
|
||||
let history: History
|
||||
let renderView: MTKView
|
||||
|
||||
var ratio: CGFloat { canvas.size.width / bounds.width }
|
||||
|
||||
private var disablesUserInteraction: Bool = false
|
||||
|
||||
required init(tool: Tool, canvas: Canvas, history: History) {
|
||||
self.tool = tool
|
||||
self.canvas = canvas
|
||||
self.history = history
|
||||
self.renderView = MTKView(frame: .zero)
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateDrawableSize(with size: CGSize) {
|
||||
renderView.drawableSize = size.multiply(by: 2.5)
|
||||
}
|
||||
|
||||
func touchBegan(on 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()
|
||||
}
|
||||
|
||||
func touchMoved(to point: CGPoint) {
|
||||
guard !disablesUserInteraction else { return }
|
||||
canvas.moveTouch(to: point.muliply(by: ratio))
|
||||
renderView.draw()
|
||||
}
|
||||
|
||||
func touchEnded(to point: CGPoint) {
|
||||
guard !disablesUserInteraction else { return }
|
||||
canvas.endTouch(at: point.muliply(by: ratio))
|
||||
renderView.draw()
|
||||
}
|
||||
|
||||
func disableUserInteraction() {
|
||||
disablesUserInteraction = true
|
||||
}
|
||||
|
||||
func enableUserInteraction() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in
|
||||
self?.disablesUserInteraction = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,8 @@ extension CanvasViewController {
|
||||
scrollView.showsHorizontalScrollIndicator = true
|
||||
scrollView.delegate = self
|
||||
scrollView.backgroundColor = .clear
|
||||
// scrollView.pinchGestureRecognizer?.cancelsTouchesInView = true
|
||||
// scrollView.pinchGestureRecognizer?.delaysTouchesEnded = true
|
||||
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(scrollView)
|
||||
@@ -97,6 +99,8 @@ extension CanvasViewController {
|
||||
scrollView.addSubview(drawingView)
|
||||
drawingView.backgroundColor = .clear
|
||||
drawingView.isUserInteractionEnabled = false
|
||||
drawingView.isMultipleTouchEnabled = true
|
||||
drawingView.isExclusiveTouch = true
|
||||
}
|
||||
|
||||
func resizeDocumentView(to newSize: CGSize? = nil) {
|
||||
@@ -187,37 +191,41 @@ 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 drawingPanGesture = PanGestureRecognizer(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)
|
||||
// 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 recognizePanGesture(_ gesture: PanGestureRecognizer) {
|
||||
// let point = gesture.location(in: drawingView)
|
||||
// switch gesture.state {
|
||||
// case .began:
|
||||
// if let initialTouch = gesture.initialTouch {
|
||||
// drawingView.touchBegan(on: initialTouch.location(in: drawingView))
|
||||
// } else {
|
||||
// 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)
|
||||
}
|
||||
// @objc func recognizeTapGesture(_ gesture: UITapGestureRecognizer) {
|
||||
// let point = gesture.location(in: drawingView)
|
||||
// drawingView.touchBegan(on: point)
|
||||
// drawingView.touchEnded(to: point)
|
||||
// }
|
||||
}
|
||||
|
||||
extension CanvasViewController: UIScrollViewDelegate {
|
||||
144
Memola/Canvas/View/Bridge/Views/DrawingView.swift
Normal file
144
Memola/Canvas/View/Bridge/Views/DrawingView.swift
Normal file
@@ -0,0 +1,144 @@
|
||||
//
|
||||
// DrawingView.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/4/24.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MetalKit
|
||||
import Foundation
|
||||
|
||||
class DrawingView: UIView {
|
||||
let tool: Tool
|
||||
let canvas: Canvas
|
||||
let history: History
|
||||
let renderView: MTKView
|
||||
|
||||
var ratio: CGFloat { canvas.size.width / bounds.width }
|
||||
|
||||
var beganTouches: Set<UITouch> = []
|
||||
|
||||
private var disablesUserInteraction: Bool = false
|
||||
|
||||
required init(tool: Tool, canvas: Canvas, history: History) {
|
||||
self.tool = tool
|
||||
self.canvas = canvas
|
||||
self.history = history
|
||||
self.renderView = MTKView(frame: .zero)
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateDrawableSize(with size: CGSize) {
|
||||
renderView.drawableSize = size.multiply(by: 2.5)
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
print("Touch Began - \(touches.count) & \(event?.allTouches?.count ?? -1)")
|
||||
super.touchesBegan(touches, with: event)
|
||||
for touch in touches {
|
||||
beganTouches.insert(touch)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
print("Touch Moved - \(beganTouches.count) & \(event?.allTouches?.count ?? -1)")
|
||||
super.touchesMoved(touches, with: event)
|
||||
validateTouch()
|
||||
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?) {
|
||||
print("Touch Ended - \(beganTouches.count)")
|
||||
super.touchesEnded(touches, with: event)
|
||||
validateTouch()
|
||||
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?) {
|
||||
print("Touch Cancelled - \(beganTouches.count)")
|
||||
super.touchesCancelled(touches, with: event)
|
||||
touchCancelled()
|
||||
}
|
||||
|
||||
// func didCreateNewStroke() -> Bool {
|
||||
// switch beganTouches.count {
|
||||
// case 0:
|
||||
// return true
|
||||
// case 1:
|
||||
// if canvas.graphicContext.currentStroke == nil {
|
||||
// guard let touch = beganTouches.first else { return false }
|
||||
// let point = touch.location(in: self)
|
||||
// touchBegan(at: point)
|
||||
// beganTouches.removeAll()
|
||||
// return true
|
||||
// } else {
|
||||
// touchCancelled()
|
||||
// return false
|
||||
// }
|
||||
// default:
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
func validateTouch() {
|
||||
if beganTouches.count == 1 {
|
||||
if canvas.graphicContext.currentStroke == nil {
|
||||
guard let touch = beganTouches.first else { return }
|
||||
let point = touch.location(in: self)
|
||||
touchBegan(at: point)
|
||||
beganTouches.removeAll()
|
||||
} else {
|
||||
touchCancelled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
history.addUndo(.stroke(stroke))
|
||||
history.resetRedo()
|
||||
}
|
||||
|
||||
func touchMoved(to point: CGPoint) {
|
||||
guard !disablesUserInteraction else { return }
|
||||
canvas.moveTouch(to: point.muliply(by: ratio))
|
||||
renderView.draw()
|
||||
}
|
||||
|
||||
func touchEnded(at point: CGPoint) {
|
||||
guard !disablesUserInteraction else { return }
|
||||
canvas.endTouch(at: point.muliply(by: ratio))
|
||||
renderView.draw()
|
||||
beganTouches.removeAll()
|
||||
}
|
||||
|
||||
func touchCancelled() {
|
||||
if canvas.graphicContext.currentStroke != nil {
|
||||
canvas.cancelTouch()
|
||||
renderView.draw()
|
||||
history.restoreUndo()
|
||||
}
|
||||
beganTouches.removeAll()
|
||||
}
|
||||
|
||||
func disableUserInteraction() {
|
||||
disablesUserInteraction = true
|
||||
}
|
||||
|
||||
func enableUserInteraction() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in
|
||||
self?.disablesUserInteraction = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user