mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-05-17 05:07:11 +02:00
138 lines
3.7 KiB
Swift
138 lines
3.7 KiB
Swift
//
|
|
// GraphicContext.swift
|
|
// Memola
|
|
//
|
|
// Created by Dscyre Scotti on 5/4/24.
|
|
//
|
|
|
|
import Combine
|
|
import MetalKit
|
|
import Foundation
|
|
|
|
protocol GraphicContextDelegate: AnyObject {
|
|
var didUpdate: PassthroughSubject<Void, Never> { get set }
|
|
}
|
|
|
|
class GraphicContext: Codable {
|
|
var strokes: [Stroke] = []
|
|
var currentStroke: Stroke?
|
|
var previousStroke: Stroke?
|
|
var currentPoint: CGPoint?
|
|
|
|
var renderType: RenderType = .finished
|
|
|
|
var vertices: [ViewPortVertex] = []
|
|
var vertexCount: Int = 4
|
|
var vertexBuffer: MTLBuffer?
|
|
|
|
weak var delegate: GraphicContextDelegate?
|
|
|
|
init() {
|
|
setViewPortVertices()
|
|
}
|
|
|
|
enum CodingKeys: CodingKey {
|
|
case strokes
|
|
}
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
self.strokes = try container.decode([Stroke].self, forKey: .strokes)
|
|
setViewPortVertices()
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(self.strokes, forKey: .strokes)
|
|
}
|
|
|
|
func setViewPortVertices() {
|
|
vertexBuffer = nil
|
|
vertices = [
|
|
ViewPortVertex(x: -1, y: -1, textCoord: CGPoint(x: 0, y: 1)),
|
|
ViewPortVertex(x: -1, y: 1, textCoord: CGPoint(x: 0, y: 0)),
|
|
ViewPortVertex(x: 1, y: -1, textCoord: CGPoint(x: 1, y: 1)),
|
|
ViewPortVertex(x: 1, y: 1, textCoord: CGPoint(x: 1, y: 0)),
|
|
]
|
|
}
|
|
|
|
func undoGraphic() {
|
|
guard !strokes.isEmpty else { return }
|
|
strokes.removeLast()
|
|
previousStroke = nil
|
|
delegate?.didUpdate.send()
|
|
}
|
|
|
|
func redoGraphic(for event: HistoryEvent) {
|
|
switch event {
|
|
case .stroke(let stroke):
|
|
strokes.append(stroke)
|
|
previousStroke = nil
|
|
}
|
|
delegate?.didUpdate.send()
|
|
}
|
|
}
|
|
|
|
extension GraphicContext: Drawable {
|
|
func prepare(device: MTLDevice) {
|
|
guard vertexBuffer == nil else {
|
|
return
|
|
}
|
|
vertexCount = vertices.count
|
|
vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout<ViewPortVertex>.stride, options: [])
|
|
}
|
|
|
|
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
|
prepare(device: device)
|
|
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
|
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertices.count)
|
|
}
|
|
}
|
|
|
|
extension GraphicContext {
|
|
func beginStroke(at point: CGPoint, pen: Pen) -> Stroke {
|
|
let stroke = Stroke(
|
|
color: pen.color,
|
|
style: pen.style,
|
|
thickness: pen.thickness
|
|
)
|
|
strokes.append(stroke)
|
|
currentStroke = stroke
|
|
currentPoint = point
|
|
currentStroke?.begin(at: point)
|
|
return stroke
|
|
}
|
|
|
|
func appendStroke(with point: CGPoint) {
|
|
guard let currentStroke else { return }
|
|
guard let currentPoint, point.distance(to: currentPoint) > currentStroke.thickness * currentStroke.style.stepRate else { return }
|
|
currentStroke.append(to: point)
|
|
self.currentPoint = point
|
|
}
|
|
|
|
func endStroke(at point: CGPoint) {
|
|
guard currentPoint != nil else { return }
|
|
currentStroke?.finish(at: point)
|
|
previousStroke = currentStroke
|
|
currentStroke = nil
|
|
self.currentPoint = nil
|
|
delegate?.didUpdate.send()
|
|
}
|
|
|
|
func cancelStroke() {
|
|
if !strokes.isEmpty {
|
|
strokes.removeLast()
|
|
}
|
|
currentStroke = nil
|
|
currentPoint = nil
|
|
}
|
|
}
|
|
|
|
extension GraphicContext {
|
|
enum RenderType {
|
|
case inProgress
|
|
case newlyFinished
|
|
case finished
|
|
}
|
|
}
|