mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-03-25 10:51:41 +01:00
refactor: replace stroke class with protocol
This commit is contained in:
@@ -77,6 +77,9 @@
|
||||
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; };
|
||||
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
|
||||
ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; };
|
||||
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BC2C00AA170045C53D /* EraserStroke.swift */; };
|
||||
ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BE2C00AB440045C53D /* Stroke.swift */; };
|
||||
ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */; };
|
||||
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECEC01A72BEE11BA006DA24C /* QuadShape.swift */; };
|
||||
ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA151F2BEF21EF00455818 /* MemoObject.swift */; };
|
||||
ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15212BEF21F500455818 /* CanvasObject.swift */; };
|
||||
@@ -159,6 +162,9 @@
|
||||
ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = "<group>"; };
|
||||
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||
ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = "<group>"; };
|
||||
ECE883BC2C00AA170045C53D /* EraserStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserStroke.swift; sourceTree = "<group>"; };
|
||||
ECE883BE2C00AB440045C53D /* Stroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = "<group>"; };
|
||||
ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeStyle.swift; sourceTree = "<group>"; };
|
||||
ECEC01A72BEE11BA006DA24C /* QuadShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadShape.swift; sourceTree = "<group>"; };
|
||||
ECFA151F2BEF21EF00455818 /* MemoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoObject.swift; sourceTree = "<group>"; };
|
||||
ECFA15212BEF21F500455818 /* CanvasObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanvasObject.swift; sourceTree = "<group>"; };
|
||||
@@ -622,6 +628,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ECA738D12BE60F7B00A4542E /* PenStroke.swift */,
|
||||
ECE883BC2C00AA170045C53D /* EraserStroke.swift */,
|
||||
);
|
||||
path = Strokes;
|
||||
sourceTree = "<group>";
|
||||
@@ -629,7 +636,9 @@
|
||||
ECE883B92C009DCA0045C53D /* Core */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ECE883BE2C00AB440045C53D /* Stroke.swift */,
|
||||
ECA738D32BE60F9100A4542E /* StrokeGenerator.swift */,
|
||||
ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */,
|
||||
);
|
||||
path = Core;
|
||||
sourceTree = "<group>";
|
||||
@@ -753,6 +762,7 @@
|
||||
ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */,
|
||||
ECA738CD2BE60F2F00A4542E /* GridContext.swift in Sources */,
|
||||
ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */,
|
||||
ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */,
|
||||
ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */,
|
||||
ECA738EA2BE6122E00A4542E /* CGPoint++.swift in Sources */,
|
||||
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
||||
@@ -778,9 +788,11 @@
|
||||
ECA738EC2BE6124E00A4542E /* CGAffineTransform++.swift in Sources */,
|
||||
EC35655C2BF0712A00A4E0BF /* Float++.swift in Sources */,
|
||||
ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */,
|
||||
ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */,
|
||||
ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */,
|
||||
ECA738F42BE612A000A4542E /* Array++.swift in Sources */,
|
||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */,
|
||||
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */,
|
||||
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
||||
ECA738C92BE60EF700A4542E /* GraphicContext.swift in Sources */,
|
||||
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */,
|
||||
|
||||
@@ -118,7 +118,7 @@ extension GraphicContext {
|
||||
let stroke = PenStroke(
|
||||
bounds: [point.x - pen.thickness, point.y - pen.thickness, point.x + pen.thickness, point.y + pen.thickness],
|
||||
color: pen.rgba,
|
||||
style: pen.strokeStyle.rawValue,
|
||||
style: pen.strokeStyle,
|
||||
createdAt: .now,
|
||||
thickness: pen.thickness
|
||||
)
|
||||
@@ -126,7 +126,7 @@ extension GraphicContext {
|
||||
let stroke = StrokeObject(\.backgroundContext)
|
||||
stroke.bounds = _stroke.bounds
|
||||
stroke.color = _stroke.color
|
||||
stroke.style = _stroke.style
|
||||
stroke.style = _stroke.style.rawValue
|
||||
stroke.thickness = _stroke.thickness
|
||||
stroke.createdAt = _stroke.createdAt
|
||||
stroke.quads = []
|
||||
@@ -143,7 +143,7 @@ extension GraphicContext {
|
||||
|
||||
func appendStroke(with point: CGPoint) {
|
||||
guard let currentStroke else { return }
|
||||
guard let currentPoint, point.distance(to: currentPoint) > currentStroke.thickness * currentStroke.penStyle.anyPenStyle.stepRate else {
|
||||
guard let currentPoint, point.distance(to: currentPoint) > currentStroke.thickness * currentStroke.penStyle.stepRate else {
|
||||
return
|
||||
}
|
||||
currentStroke.append(to: point)
|
||||
@@ -153,9 +153,7 @@ extension GraphicContext {
|
||||
func endStroke(at point: CGPoint) {
|
||||
guard currentPoint != nil, let currentStroke else { return }
|
||||
currentStroke.finish(at: point)
|
||||
let batchIndex = currentStroke.batchIndex
|
||||
let quads = Array(currentStroke.quads[batchIndex..<currentStroke.quads.count])
|
||||
currentStroke.saveQuads(for: quads)
|
||||
currentStroke.saveQuads(to: currentStroke.quads.endIndex)
|
||||
withPersistence(\.backgroundContext) { context in
|
||||
try context.saveIfNeeded()
|
||||
if let stroke = currentStroke.object {
|
||||
|
||||
105
Memola/Canvas/Geometries/Stroke/Core/Stroke.swift
Normal file
105
Memola/Canvas/Geometries/Stroke/Core/Stroke.swift
Normal file
@@ -0,0 +1,105 @@
|
||||
//
|
||||
// Stroke.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/24/24.
|
||||
//
|
||||
|
||||
import MetalKit
|
||||
import Foundation
|
||||
|
||||
protocol Stroke: AnyObject, Drawable {
|
||||
var bounds: [CGFloat] { get set }
|
||||
var color: [CGFloat] { get set }
|
||||
var style: StrokeStyle { get set }
|
||||
var createdAt: Date { get set }
|
||||
var thickness: CGFloat { get set }
|
||||
var quads: [Quad] { get set }
|
||||
var penStyle: any PenStyle { get set }
|
||||
|
||||
var batchIndex: Int { get set }
|
||||
var quadIndex: Int { get set }
|
||||
var keyPoints: [CGPoint] { get set }
|
||||
var movingAverage: MovingAverage { get set }
|
||||
|
||||
var texture: MTLTexture? { get set }
|
||||
var indexBuffer: MTLBuffer? { get set }
|
||||
var vertexBuffer: MTLBuffer? { get set }
|
||||
|
||||
func begin(at point: CGPoint)
|
||||
func append(to point: CGPoint)
|
||||
func finish(at point: CGPoint)
|
||||
|
||||
func loadQuads()
|
||||
func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape)
|
||||
func removeQuads(from index: Int)
|
||||
func saveQuads(to index: Int)
|
||||
}
|
||||
|
||||
extension Stroke {
|
||||
var isEmpty: Bool { quads.isEmpty }
|
||||
|
||||
var strokeBounds: CGRect {
|
||||
let x = bounds[0]
|
||||
let y = bounds[1]
|
||||
let width = bounds[2] - x
|
||||
let height = bounds[3] - y
|
||||
return CGRect(x: x, y: y, width: width, height: height)
|
||||
}
|
||||
|
||||
func isVisible(in bounds: CGRect) -> Bool {
|
||||
bounds.contains(strokeBounds) || bounds.intersects(strokeBounds)
|
||||
}
|
||||
|
||||
func begin(at point: CGPoint) {
|
||||
penStyle.generator.begin(at: point, on: self)
|
||||
}
|
||||
|
||||
func append(to point: CGPoint) {
|
||||
penStyle.generator.append(to: point, on: self)
|
||||
}
|
||||
|
||||
func finish(at point: CGPoint) {
|
||||
penStyle.generator.finish(at: point, on: self)
|
||||
keyPoints.removeAll()
|
||||
}
|
||||
|
||||
func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape) {
|
||||
let quad = Quad(
|
||||
origin: point,
|
||||
size: thickness,
|
||||
rotation: rotation,
|
||||
shape: shape.rawValue,
|
||||
color: color
|
||||
)
|
||||
quads.append(quad)
|
||||
}
|
||||
|
||||
func removeQuads(from index: Int) {
|
||||
let dropCount = quads.endIndex - max(1, index)
|
||||
quads.removeLast(dropCount)
|
||||
}
|
||||
}
|
||||
|
||||
extension Stroke {
|
||||
func prepare(device: MTLDevice) {
|
||||
guard texture != nil else { return }
|
||||
texture = penStyle.loadTexture(on: device)
|
||||
}
|
||||
|
||||
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
||||
guard !isEmpty, let indexBuffer else { return }
|
||||
prepare(device: device)
|
||||
renderEncoder.setFragmentTexture(texture, index: 0)
|
||||
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||
renderEncoder.drawIndexedPrimitives(
|
||||
type: .triangle,
|
||||
indexCount: quads.endIndex * 6,
|
||||
indexType: .uint32,
|
||||
indexBuffer: indexBuffer,
|
||||
indexBufferOffset: 0
|
||||
)
|
||||
self.vertexBuffer = nil
|
||||
self.indexBuffer = nil
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ protocol StrokeGenerator {
|
||||
|
||||
var configuration: Configuration { get set }
|
||||
|
||||
func begin(at point: CGPoint, on stroke: PenStroke)
|
||||
func append(to point: CGPoint, on stroke: PenStroke)
|
||||
func finish(at point: CGPoint, on stroke: PenStroke)
|
||||
func begin(at point: CGPoint, on stroke: Stroke)
|
||||
func append(to point: CGPoint, on stroke: Stroke)
|
||||
func finish(at point: CGPoint, on stroke: Stroke)
|
||||
}
|
||||
|
||||
22
Memola/Canvas/Geometries/Stroke/Core/StrokeStyle.swift
Normal file
22
Memola/Canvas/Geometries/Stroke/Core/StrokeStyle.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// StrokeStyle.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/24/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum StrokeStyle: Int16 {
|
||||
case marker
|
||||
case eraser
|
||||
|
||||
var penStyle: any PenStyle {
|
||||
switch self {
|
||||
case .marker:
|
||||
MarkerPenStyle.marker
|
||||
case .eraser:
|
||||
EraserPenStyle.eraser
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ import Foundation
|
||||
struct SolidPointStrokeGenerator: StrokeGenerator {
|
||||
var configuration: Configuration
|
||||
|
||||
func begin(at point: CGPoint, on stroke: PenStroke) {
|
||||
func begin(at point: CGPoint, on stroke: Stroke) {
|
||||
let point = stroke.movingAverage.addPoint(point)
|
||||
stroke.keyPoints.append(point)
|
||||
addPoint(point, on: stroke)
|
||||
}
|
||||
|
||||
func append(to point: CGPoint, on stroke: PenStroke) {
|
||||
func append(to point: CGPoint, on stroke: Stroke) {
|
||||
guard stroke.keyPoints.endIndex > 0 else {
|
||||
return
|
||||
}
|
||||
@@ -49,7 +49,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
func finish(at point: CGPoint, on stroke: PenStroke) {
|
||||
func finish(at point: CGPoint, on stroke: Stroke) {
|
||||
switch stroke.keyPoints.endIndex {
|
||||
case 0...1:
|
||||
break
|
||||
@@ -58,7 +58,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private func smoothOutPath(on stroke: PenStroke) {
|
||||
private func smoothOutPath(on stroke: Stroke) {
|
||||
stroke.removeQuads(from: stroke.quadIndex + 1)
|
||||
adjustKeyPoint(on: stroke)
|
||||
switch stroke.keyPoints.endIndex {
|
||||
@@ -79,7 +79,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
||||
stroke.quadIndex = stroke.quads.endIndex - 1
|
||||
}
|
||||
|
||||
private func adjustKeyPoint(on stroke: PenStroke) {
|
||||
private func adjustKeyPoint(on stroke: Stroke) {
|
||||
let index = stroke.keyPoints.endIndex - 1
|
||||
let prev = stroke.keyPoints[index - 1]
|
||||
let current = stroke.keyPoints[index]
|
||||
@@ -89,7 +89,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
||||
stroke.keyPoints[index] = point
|
||||
}
|
||||
|
||||
private func addPoint(_ point: CGPoint, on stroke: PenStroke) {
|
||||
private func addPoint(_ point: CGPoint, on stroke: Stroke) {
|
||||
let rotation: CGFloat
|
||||
switch configuration.rotation {
|
||||
case .fixed:
|
||||
@@ -100,14 +100,14 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
||||
stroke.addQuad(at: point, rotation: rotation, shape: .rounded)
|
||||
}
|
||||
|
||||
private func addCurve(from start: CGPoint, to end: CGPoint, by control: CGPoint, on stroke: PenStroke) {
|
||||
private func addCurve(from start: CGPoint, to end: CGPoint, by control: CGPoint, on stroke: Stroke) {
|
||||
let distance = start.distance(to: end)
|
||||
let factor: CGFloat
|
||||
switch configuration.granularity {
|
||||
case .automatic:
|
||||
factor = min(5, 1 / (stroke.thickness * 1 / 50))
|
||||
case .fixed:
|
||||
factor = 1 / (stroke.thickness * stroke.penStyle.anyPenStyle.stepRate)
|
||||
factor = 1 / (stroke.thickness * stroke.penStyle.stepRate)
|
||||
case .none:
|
||||
factor = 1 / (stroke.thickness * 10 / 500)
|
||||
}
|
||||
|
||||
13
Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift
Normal file
13
Memola/Canvas/Geometries/Stroke/Strokes/EraserStroke.swift
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// EraserStroke.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/24/24.
|
||||
//
|
||||
|
||||
import MetalKit
|
||||
import Foundation
|
||||
|
||||
final class EraserStroke: Stroke, @unchecked Sendable {
|
||||
|
||||
}
|
||||
@@ -9,31 +9,30 @@ import MetalKit
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
final class PenStroke: @unchecked Sendable {
|
||||
var object: StrokeObject?
|
||||
final class PenStroke: Stroke, @unchecked Sendable {
|
||||
var bounds: [CGFloat]
|
||||
var color: [CGFloat]
|
||||
var style: Int16
|
||||
var style: StrokeStyle
|
||||
var createdAt: Date
|
||||
var thickness: CGFloat
|
||||
var quads: [Quad]
|
||||
var penStyle: Style
|
||||
var penStyle: any PenStyle
|
||||
|
||||
init(object: StrokeObject) {
|
||||
self.object = object
|
||||
self.bounds = object.bounds
|
||||
self.color = object.color
|
||||
self.style = object.style
|
||||
self.createdAt = object.createdAt
|
||||
self.thickness = object.thickness
|
||||
self.quads = []
|
||||
self.penStyle = Style(rawValue: style) ?? .marker
|
||||
}
|
||||
var batchIndex: Int = 0
|
||||
var quadIndex: Int = -1
|
||||
var keyPoints: [CGPoint] = []
|
||||
var movingAverage: MovingAverage = MovingAverage(windowSize: 3)
|
||||
|
||||
var texture: (any MTLTexture)?
|
||||
var indexBuffer: (any MTLBuffer)?
|
||||
var vertexBuffer: (any MTLBuffer)?
|
||||
|
||||
var object: StrokeObject?
|
||||
|
||||
init(
|
||||
bounds: [CGFloat],
|
||||
color: [CGFloat],
|
||||
style: Int16,
|
||||
style: StrokeStyle,
|
||||
createdAt: Date,
|
||||
thickness: CGFloat,
|
||||
quads: [Quad] = []
|
||||
@@ -44,53 +43,24 @@ final class PenStroke: @unchecked Sendable {
|
||||
self.createdAt = createdAt
|
||||
self.thickness = thickness
|
||||
self.quads = quads
|
||||
self.penStyle = Style(rawValue: style) ?? .marker
|
||||
self.penStyle = style.penStyle
|
||||
}
|
||||
|
||||
var batchIndex: Int = 0
|
||||
var quadIndex: Int = -1
|
||||
var keyPoints: [CGPoint] = []
|
||||
|
||||
let movingAverage = MovingAverage(windowSize: 3)
|
||||
|
||||
var texture: MTLTexture?
|
||||
var indexBuffer: MTLBuffer?
|
||||
var vertexBuffer: MTLBuffer?
|
||||
|
||||
var isEmpty: Bool { quads.isEmpty }
|
||||
var strokeBounds: CGRect {
|
||||
let x = bounds[0]
|
||||
let y = bounds[1]
|
||||
let width = bounds[2] - x
|
||||
let height = bounds[3] - y
|
||||
return CGRect(x: x, y: y, width: width, height: height)
|
||||
convenience init(object: StrokeObject) {
|
||||
let style = StrokeStyle(rawValue: object.style) ?? .marker
|
||||
self.init(
|
||||
bounds: object.bounds,
|
||||
color: object.color,
|
||||
style: style,
|
||||
createdAt: object.createdAt,
|
||||
thickness: object.thickness
|
||||
)
|
||||
self.object = object
|
||||
}
|
||||
|
||||
func isVisible(in bounds: CGRect) -> Bool {
|
||||
bounds.contains(strokeBounds) || bounds.intersects(strokeBounds)
|
||||
}
|
||||
|
||||
func begin(at point: CGPoint) {
|
||||
penStyle.anyPenStyle.generator.begin(at: point, on: self)
|
||||
}
|
||||
|
||||
func append(to point: CGPoint) {
|
||||
penStyle.anyPenStyle.generator.append(to: point, on: self)
|
||||
}
|
||||
|
||||
func finish(at point: CGPoint) {
|
||||
penStyle.anyPenStyle.generator.finish(at: point, on: self)
|
||||
keyPoints.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
extension PenStroke {
|
||||
func loadQuads() {
|
||||
guard let object else { return }
|
||||
quads = object.quads.compactMap { quad in
|
||||
guard let quad = quad as? QuadObject else { return nil }
|
||||
return Quad(object: quad)
|
||||
}
|
||||
loadQuads(from: object)
|
||||
}
|
||||
|
||||
func loadQuads(from object: StrokeObject) {
|
||||
@@ -100,30 +70,19 @@ extension PenStroke {
|
||||
}
|
||||
}
|
||||
|
||||
func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape) {
|
||||
let quad = Quad(
|
||||
origin: point,
|
||||
size: thickness,
|
||||
rotation: rotation,
|
||||
shape: shape.rawValue,
|
||||
color: color
|
||||
)
|
||||
quads.append(quad)
|
||||
}
|
||||
|
||||
func removeQuads(from index: Int) {
|
||||
let dropCount = quads.endIndex - max(1, index)
|
||||
quads.removeLast(dropCount)
|
||||
let quads = Array(quads[batchIndex..<index])
|
||||
batchIndex = index
|
||||
saveQuads(for: quads)
|
||||
saveQuads(to: index)
|
||||
}
|
||||
|
||||
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])
|
||||
func saveQuads(to index: Int) {
|
||||
let quads = Array(quads[batchIndex..<index])
|
||||
batchIndex = index
|
||||
withPersistence(\.backgroundContext) { [weak self, object] context in
|
||||
guard let self else { return }
|
||||
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
|
||||
@@ -144,43 +103,3 @@ extension PenStroke {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PenStroke: Drawable {
|
||||
func prepare(device: MTLDevice) {
|
||||
if texture == nil {
|
||||
texture = penStyle.anyPenStyle.loadTexture(on: device)
|
||||
}
|
||||
}
|
||||
|
||||
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
||||
guard !isEmpty, let indexBuffer else { return }
|
||||
prepare(device: device)
|
||||
renderEncoder.setFragmentTexture(texture, index: 0)
|
||||
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||
renderEncoder.drawIndexedPrimitives(
|
||||
type: .triangle,
|
||||
indexCount: quads.endIndex * 6,
|
||||
indexType: .uint32,
|
||||
indexBuffer: indexBuffer,
|
||||
indexBufferOffset: 0
|
||||
)
|
||||
self.vertexBuffer = nil
|
||||
self.indexBuffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension PenStroke {
|
||||
enum Style: Int16 {
|
||||
case marker
|
||||
case eraser
|
||||
|
||||
var anyPenStyle: any PenStyle {
|
||||
switch self {
|
||||
case .marker:
|
||||
return MarkerPenStyle.marker
|
||||
case .eraser:
|
||||
return EraserPenStyle.eraser
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ class CacheRenderPass: RenderPass {
|
||||
|
||||
let graphicContext = canvas.graphicContext
|
||||
if let stroke = graphicContext.currentStroke {
|
||||
switch stroke.penStyle {
|
||||
switch stroke.style {
|
||||
case .eraser:
|
||||
eraserRenderPass.stroke = stroke
|
||||
eraserRenderPass.descriptor = descriptor
|
||||
|
||||
@@ -52,7 +52,7 @@ class GraphicRenderPass: RenderPass {
|
||||
guard stroke.isVisible(in: canvas.bounds) else { continue }
|
||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||
clearsTexture = false
|
||||
switch stroke.penStyle {
|
||||
switch stroke.style {
|
||||
case .eraser:
|
||||
eraserRenderPass.stroke = stroke
|
||||
eraserRenderPass.descriptor = descriptor
|
||||
@@ -71,7 +71,7 @@ class GraphicRenderPass: RenderPass {
|
||||
if let stroke = graphicContext.previousStroke {
|
||||
descriptor.colorAttachments[0].loadAction = clearsTexture ? .clear : .load
|
||||
clearsTexture = false
|
||||
switch stroke.penStyle {
|
||||
switch stroke.style {
|
||||
case .eraser:
|
||||
eraserRenderPass.stroke = stroke
|
||||
eraserRenderPass.descriptor = descriptor
|
||||
|
||||
@@ -43,14 +43,14 @@ class Pen: NSObject, ObservableObject, Identifiable {
|
||||
init(object: PenObject) {
|
||||
self.object = object
|
||||
self.id = object.objectID.uriRepresentation().absoluteString
|
||||
self.style = (PenStroke.Style(rawValue: object.style) ?? .marker).anyPenStyle
|
||||
self.style = (StrokeStyle(rawValue: object.style) ?? .marker).penStyle
|
||||
self.rgba = object.color
|
||||
self.thickness = object.thickness
|
||||
self.isSelected = object.isSelected
|
||||
super.init()
|
||||
}
|
||||
|
||||
var strokeStyle: PenStroke.Style {
|
||||
var strokeStyle: StrokeStyle {
|
||||
style.strokeStyle
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ protocol PenStyle {
|
||||
var color: [CGFloat] { get }
|
||||
var stepRate: CGFloat { get }
|
||||
var generator: any StrokeGenerator { get }
|
||||
var strokeStyle: StrokeStyle { get }
|
||||
}
|
||||
|
||||
extension PenStyle {
|
||||
@@ -23,15 +24,4 @@ extension PenStyle {
|
||||
func loadTexture(on device: MTLDevice) -> MTLTexture? {
|
||||
Textures.createPenTexture(with: textureName, on: device)
|
||||
}
|
||||
|
||||
var strokeStyle: PenStroke.Style {
|
||||
switch self {
|
||||
case is MarkerPenStyle:
|
||||
return .marker
|
||||
case is EraserPenStyle:
|
||||
return .eraser
|
||||
default:
|
||||
return .marker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ struct EraserPenStyle: PenStyle {
|
||||
var generator: any StrokeGenerator {
|
||||
SolidPointStrokeGenerator(configuration: .init())
|
||||
}
|
||||
|
||||
var strokeStyle: StrokeStyle { .eraser }
|
||||
}
|
||||
|
||||
extension PenStyle where Self == EraserPenStyle {
|
||||
|
||||
@@ -23,6 +23,8 @@ struct MarkerPenStyle: PenStyle {
|
||||
var generator: any StrokeGenerator {
|
||||
SolidPointStrokeGenerator(configuration: .init())
|
||||
}
|
||||
|
||||
var strokeStyle: StrokeStyle { .marker }
|
||||
}
|
||||
|
||||
extension PenStyle where Self == MarkerPenStyle {
|
||||
|
||||
Reference in New Issue
Block a user