mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-04-25 10:08:34 +02:00
feat: add quad entity instead of storing in array
This commit is contained in:
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
||||||
EC771E602BEB6EE50053CC68 /* QuadValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC771E5F2BEB6EE50053CC68 /* QuadValueTransformer.swift */; };
|
|
||||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
||||||
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
|
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
|
||||||
EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; };
|
EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; };
|
||||||
@@ -63,16 +62,15 @@
|
|||||||
ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F12BE6128F00A4542E /* Collection++.swift */; };
|
ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F12BE6128F00A4542E /* Collection++.swift */; };
|
||||||
ECA738F42BE612A000A4542E /* Array++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F32BE612A000A4542E /* Array++.swift */; };
|
ECA738F42BE612A000A4542E /* Array++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F32BE612A000A4542E /* Array++.swift */; };
|
||||||
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; };
|
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; };
|
||||||
ECA738F82BE612EB00A4542E /* StrokeQuad.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F72BE612EB00A4542E /* StrokeQuad.swift */; };
|
|
||||||
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
|
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
|
||||||
ECA739012BE61D9C00A4542E /* MemolaModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FF2BE61D9C00A4542E /* MemolaModel.xcdatamodeld */; };
|
ECA739012BE61D9C00A4542E /* MemolaModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FF2BE61D9C00A4542E /* MemolaModel.xcdatamodeld */; };
|
||||||
ECA739052BE61E3100A4542E /* Memo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739042BE61E3100A4542E /* Memo.swift */; };
|
ECA739052BE61E3100A4542E /* Memo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739042BE61E3100A4542E /* Memo.swift */; };
|
||||||
ECA739082BE623F300A4542E /* PenToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenToolView.swift */; };
|
ECA739082BE623F300A4542E /* PenToolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenToolView.swift */; };
|
||||||
|
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECEC01A72BEE11BA006DA24C /* QuadShape.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
||||||
EC771E5F2BEB6EE50053CC68 /* QuadValueTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadValueTransformer.swift; sourceTree = "<group>"; };
|
|
||||||
EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = "<group>"; };
|
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = "<group>"; };
|
||||||
EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
@@ -128,11 +126,11 @@
|
|||||||
ECA738F12BE6128F00A4542E /* Collection++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = "<group>"; };
|
ECA738F12BE6128F00A4542E /* Collection++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = "<group>"; };
|
||||||
ECA738F32BE612A000A4542E /* Array++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = "<group>"; };
|
ECA738F32BE612A000A4542E /* Array++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = "<group>"; };
|
||||||
ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = "<group>"; };
|
ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = "<group>"; };
|
||||||
ECA738F72BE612EB00A4542E /* StrokeQuad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeQuad.swift; sourceTree = "<group>"; };
|
|
||||||
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||||
ECA739002BE61D9C00A4542E /* MemolaModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MemolaModel.xcdatamodel; sourceTree = "<group>"; };
|
ECA739002BE61D9C00A4542E /* MemolaModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MemolaModel.xcdatamodel; sourceTree = "<group>"; };
|
||||||
ECA739042BE61E3100A4542E /* Memo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memo.swift; sourceTree = "<group>"; };
|
ECA739042BE61E3100A4542E /* Memo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memo.swift; sourceTree = "<group>"; };
|
||||||
ECA739072BE623F300A4542E /* PenToolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenToolView.swift; sourceTree = "<group>"; };
|
ECA739072BE623F300A4542E /* PenToolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenToolView.swift; sourceTree = "<group>"; };
|
||||||
|
ECEC01A72BEE11BA006DA24C /* QuadShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadShape.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -162,14 +160,6 @@
|
|||||||
path = ViewController;
|
path = ViewController;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
EC771E5C2BEB37FC0053CC68 /* Transformers */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
EC771E5F2BEB6EE50053CC68 /* QuadValueTransformer.swift */,
|
|
||||||
);
|
|
||||||
path = Transformers;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
EC7F6BDF2BE5E6E300A34A7B = {
|
EC7F6BDF2BE5E6E300A34A7B = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -304,8 +294,8 @@
|
|||||||
ECA738982BE6015700A4542E /* Primitives */ = {
|
ECA738982BE6015700A4542E /* Primitives */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECA738F72BE612EB00A4542E /* StrokeQuad.swift */,
|
|
||||||
EC4538882BEBCAE000A86FEC /* Quad.swift */,
|
EC4538882BEBCAE000A86FEC /* Quad.swift */,
|
||||||
|
ECEC01A72BEE11BA006DA24C /* QuadShape.swift */,
|
||||||
);
|
);
|
||||||
path = Primitives;
|
path = Primitives;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -463,7 +453,6 @@
|
|||||||
ECA738FA2BE61B1700A4542E /* Persistence */ = {
|
ECA738FA2BE61B1700A4542E /* Persistence */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
EC771E5C2BEB37FC0053CC68 /* Transformers */,
|
|
||||||
ECA739022BE61DE700A4542E /* Core */,
|
ECA739022BE61DE700A4542E /* Core */,
|
||||||
);
|
);
|
||||||
path = Persistence;
|
path = Persistence;
|
||||||
@@ -572,7 +561,6 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
EC771E602BEB6EE50053CC68 /* QuadValueTransformer.swift in Sources */,
|
|
||||||
ECA738B02BE60D0B00A4542E /* CanvasViewController.swift in Sources */,
|
ECA738B02BE60D0B00A4542E /* CanvasViewController.swift in Sources */,
|
||||||
ECA738E42BE6110800A4542E /* Drawable.swift in Sources */,
|
ECA738E42BE6110800A4542E /* Drawable.swift in Sources */,
|
||||||
ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */,
|
ECA738AD2BE60CC600A4542E /* DrawingView.swift in Sources */,
|
||||||
@@ -594,6 +582,7 @@
|
|||||||
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
||||||
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
||||||
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
||||||
|
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
||||||
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
||||||
ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */,
|
ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */,
|
||||||
ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */,
|
ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */,
|
||||||
@@ -628,7 +617,6 @@
|
|||||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
|
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
|
||||||
ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */,
|
ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */,
|
||||||
ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */,
|
ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */,
|
||||||
ECA738F82BE612EB00A4542E /* StrokeQuad.swift in Sources */,
|
|
||||||
ECA738972BE6014200A4542E /* Graphic.metal in Sources */,
|
ECA738972BE6014200A4542E /* Graphic.metal in Sources */,
|
||||||
ECA7388A2BE6006A00A4542E /* PipelineStates.swift in Sources */,
|
ECA7388A2BE6006A00A4542E /* PipelineStates.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ struct MemolaApp: App {
|
|||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
MemosView()
|
MemosView()
|
||||||
.environment(\.managedObjectContext, Persistence.shared.viewContext)
|
.environment(\.managedObjectContext, Persistence.context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,11 +44,7 @@ final class GraphicContext: NSManagedObject {
|
|||||||
strokes.remove(stroke)
|
strokes.remove(stroke)
|
||||||
stroke.graphicContext = nil
|
stroke.graphicContext = nil
|
||||||
previousStroke = nil
|
previousStroke = nil
|
||||||
do {
|
Persistence.saveIfNeeded()
|
||||||
try Persistence.shared.viewContext.save()
|
|
||||||
} catch {
|
|
||||||
NSLog("[Memola] - \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func redoGraphic(for event: HistoryEvent) {
|
func redoGraphic(for event: HistoryEvent) {
|
||||||
@@ -58,11 +54,7 @@ final class GraphicContext: NSManagedObject {
|
|||||||
stroke.graphicContext = self
|
stroke.graphicContext = self
|
||||||
previousStroke = nil
|
previousStroke = nil
|
||||||
}
|
}
|
||||||
do {
|
Persistence.saveIfNeeded()
|
||||||
try Persistence.shared.viewContext.save()
|
|
||||||
} catch {
|
|
||||||
NSLog("[Memola] - \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,13 +76,13 @@ extension GraphicContext: Drawable {
|
|||||||
|
|
||||||
extension GraphicContext {
|
extension GraphicContext {
|
||||||
func beginStroke(at point: CGPoint, pen: Pen) -> Stroke {
|
func beginStroke(at point: CGPoint, pen: Pen) -> Stroke {
|
||||||
let stroke = Stroke(context: Persistence.shared.viewContext)
|
let stroke = Stroke(context: Persistence.context)
|
||||||
stroke.id = UUID()
|
stroke.id = UUID()
|
||||||
stroke.color = pen.color
|
stroke.color = pen.color
|
||||||
stroke.style = pen.strokeStyle.rawValue
|
stroke.style = pen.strokeStyle.rawValue
|
||||||
stroke.thickness = pen.thickness
|
stroke.thickness = pen.thickness
|
||||||
stroke.createdAt = .now
|
stroke.createdAt = .now
|
||||||
stroke.strokeQuads = []
|
stroke.quads = []
|
||||||
stroke.graphicContext = self
|
stroke.graphicContext = self
|
||||||
strokes.add(stroke)
|
strokes.add(stroke)
|
||||||
currentStroke = stroke
|
currentStroke = stroke
|
||||||
@@ -109,12 +101,7 @@ extension GraphicContext {
|
|||||||
func endStroke(at point: CGPoint) {
|
func endStroke(at point: CGPoint) {
|
||||||
guard currentPoint != nil, let currentStroke else { return }
|
guard currentPoint != nil, let currentStroke else { return }
|
||||||
currentStroke.finish(at: point)
|
currentStroke.finish(at: point)
|
||||||
currentStroke.saveQuads()
|
Persistence.saveIfNeeded()
|
||||||
do {
|
|
||||||
try Persistence.shared.viewContext.save()
|
|
||||||
} catch {
|
|
||||||
NSLog("[Memola] - \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
previousStroke = currentStroke
|
previousStroke = currentStroke
|
||||||
self.currentStroke = nil
|
self.currentStroke = nil
|
||||||
self.currentPoint = nil
|
self.currentPoint = nil
|
||||||
@@ -122,14 +109,11 @@ extension GraphicContext {
|
|||||||
|
|
||||||
func cancelStroke() {
|
func cancelStroke() {
|
||||||
if let stroke = strokes.lastObject as? Stroke {
|
if let stroke = strokes.lastObject as? Stroke {
|
||||||
do {
|
Persistence.performe { context in
|
||||||
let viewContext = Persistence.shared.viewContext
|
|
||||||
strokes.remove(stroke)
|
strokes.remove(stroke)
|
||||||
viewContext.delete(stroke)
|
context.delete(stroke)
|
||||||
try viewContext.save()
|
|
||||||
} catch {
|
|
||||||
NSLog("[Memola] - \(error.localizedDescription)")
|
|
||||||
}
|
}
|
||||||
|
Persistence.saveIfNeeded()
|
||||||
}
|
}
|
||||||
currentStroke = nil
|
currentStroke = nil
|
||||||
currentPoint = nil
|
currentPoint = nil
|
||||||
|
|||||||
@@ -44,22 +44,15 @@ extension Canvas {
|
|||||||
func load() {
|
func load() {
|
||||||
state = .loading
|
state = .loading
|
||||||
let start = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5)))
|
let start = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5)))
|
||||||
Task(priority: .high) { [start] in
|
for stroke in graphicContext.strokes {
|
||||||
await withTaskGroup(of: Void.self) { taskGroup in
|
if let stroke = stroke as? Stroke {
|
||||||
for stroke in graphicContext.strokes {
|
stroke.loadVertices()
|
||||||
guard let stroke = stroke as? Stroke else { continue }
|
NSLog("[Memola] - \(stroke.quads.count) quads")
|
||||||
taskGroup.addTask {
|
|
||||||
stroke.loadVertices()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let end = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5)))
|
|
||||||
NSLog("[Memola] - Loaded from \(start) to \(end)")
|
|
||||||
await MainActor.run {
|
|
||||||
state = .loaded
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let end = Date().formatted(.dateTime.minute().second().secondFraction(.fractional(5)))
|
||||||
|
NSLog("[Memola] - Loaded from \(start) to \(end)")
|
||||||
|
state = .loaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,37 +5,38 @@
|
|||||||
// Created by Dscyre Scotti on 5/8/24.
|
// Created by Dscyre Scotti on 5/8/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Quad: Codable {
|
@objc(Quad)
|
||||||
var origin: CGPoint
|
class Quad: NSManagedObject {
|
||||||
var color: [CGFloat]
|
@NSManaged var id: UUID
|
||||||
var size: CGFloat
|
@NSManaged var originX: CGFloat
|
||||||
var rotation: CGFloat
|
@NSManaged var originY: CGFloat
|
||||||
var shape: QuadShape
|
@NSManaged var size: CGFloat
|
||||||
|
@NSManaged var rotation: CGFloat
|
||||||
|
@NSManaged var shape: Int16
|
||||||
|
@NSManaged var stroke: Stroke?
|
||||||
|
|
||||||
init(origin: CGPoint, size: CGFloat, color: [CGFloat], rotation: CGFloat, shape: QuadShape = .rounded) {
|
var origin: CGPoint {
|
||||||
self.origin = origin
|
get { CGPoint(x: originX, y: originY) }
|
||||||
self.size = size
|
set {
|
||||||
self.color = color
|
originX = newValue.x
|
||||||
self.rotation = rotation
|
originY = newValue.y
|
||||||
self.shape = shape
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateVertices() -> [QuadVertex] {
|
|
||||||
switch shape {
|
|
||||||
case .rounded:
|
|
||||||
generateRoundedQuad()
|
|
||||||
case .squared:
|
|
||||||
generateSquaredQuad()
|
|
||||||
case let .calligraphic(vFactor, hFactor):
|
|
||||||
generateCalligraphicQuad(vFactor: vFactor, hFactor: hFactor)
|
|
||||||
case let .trapezoid(topFactor, bottomFactor, heightFactor):
|
|
||||||
generateTrapezoidQuad(topFactor: topFactor, bottomFactor: bottomFactor, heightFactor: heightFactor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRoundedQuad() -> [QuadVertex] {
|
func generateVertices(_ color: [CGFloat]) -> [QuadVertex] {
|
||||||
|
guard let shape = QuadShape.init(rawValue: shape) else { return [] }
|
||||||
|
switch shape {
|
||||||
|
case .rounded:
|
||||||
|
return generateRoundedQuad(color)
|
||||||
|
case .squared:
|
||||||
|
return generateSquaredQuad(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRoundedQuad(_ color: [CGFloat]) -> [QuadVertex] {
|
||||||
let halfSize = size * 0.5
|
let halfSize = size * 0.5
|
||||||
return [
|
return [
|
||||||
QuadVertex(x: origin.x - halfSize, y: origin.y - halfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation),
|
QuadVertex(x: origin.x - halfSize, y: origin.y - halfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation),
|
||||||
@@ -47,7 +48,7 @@ struct Quad: Codable {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSquaredQuad() -> [QuadVertex] {
|
func generateSquaredQuad(_ color: [CGFloat]) -> [QuadVertex] {
|
||||||
let vHalfSize = size * 0.5
|
let vHalfSize = size * 0.5
|
||||||
let hHalfSize = size * 0.15
|
let hHalfSize = size * 0.15
|
||||||
return [
|
return [
|
||||||
@@ -59,38 +60,4 @@ struct Quad: Codable {
|
|||||||
QuadVertex(x: origin.x + hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation)
|
QuadVertex(x: origin.x + hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateCalligraphicQuad(vFactor: CGFloat, hFactor: CGFloat) -> [QuadVertex] {
|
|
||||||
let vHalfSize = size * vFactor * 0.5
|
|
||||||
let hHalfSize = size * hFactor * 0.5
|
|
||||||
return [
|
|
||||||
QuadVertex(x: origin.x - hHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x + hHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x - hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x + hHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x - hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x + hHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateTrapezoidQuad(topFactor: CGFloat, bottomFactor: CGFloat, heightFactor: CGFloat) -> [QuadVertex] {
|
|
||||||
let vHalfSize = size * heightFactor * 0.5
|
|
||||||
let hTopHalfSize = size * topFactor * 0.5
|
|
||||||
let hBottomHalfSize = size * bottomFactor * 0.5
|
|
||||||
return [
|
|
||||||
QuadVertex(x: origin.x - hTopHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 0, y: 0), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x + hBottomHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x - hTopHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x + hBottomHalfSize, y: origin.y - vHalfSize, textCoord: CGPoint(x: 1, y: 0), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x - hTopHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 0, y: 1), color: color, origin: origin, rotation: rotation),
|
|
||||||
QuadVertex(x: origin.x + hBottomHalfSize, y: origin.y + vHalfSize, textCoord: CGPoint(x: 1, y: 1), color: color, origin: origin, rotation: rotation)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum QuadShape: Codable {
|
|
||||||
case rounded
|
|
||||||
case squared
|
|
||||||
case calligraphic(CGFloat, CGFloat)
|
|
||||||
case trapezoid(topFactor: CGFloat, bottomFactor: CGFloat, heightFactor: CGFloat)
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
Memola/Canvas/Geometries/Primitives/QuadShape.swift
Normal file
13
Memola/Canvas/Geometries/Primitives/QuadShape.swift
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// QuadShape.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 5/10/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum QuadShape: Int16 {
|
||||||
|
case rounded
|
||||||
|
case squared
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
//
|
|
||||||
// StrokeQuad.swift
|
|
||||||
// Memola
|
|
||||||
//
|
|
||||||
// Created by Dscyre Scotti on 5/4/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import MetalKit
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
final class StrokeQuad: NSObject, Codable {
|
|
||||||
var quad: Quad
|
|
||||||
|
|
||||||
init(quad: Quad) {
|
|
||||||
self.quad = quad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
let control = CGPoint.middle(p1: start, p2: end)
|
let control = CGPoint.middle(p1: start, p2: end)
|
||||||
addCurve(from: start, to: end, by: control, on: stroke)
|
addCurve(from: start, to: end, by: control, on: stroke)
|
||||||
case 3:
|
case 3:
|
||||||
discardVertices(upto: stroke.vertexIndex, on: stroke)
|
discardVertices(upto: stroke.vertexIndex, quadIndex: stroke.quadIndex, on: stroke)
|
||||||
let index = stroke.keyPoints.count - 1
|
let index = stroke.keyPoints.count - 1
|
||||||
var start = stroke.keyPoints[index - 2]
|
var start = stroke.keyPoints[index - 2]
|
||||||
var end = CGPoint.middle(p1: stroke.keyPoints[index - 2], p2: stroke.keyPoints[index - 1])
|
var end = CGPoint.middle(p1: stroke.keyPoints[index - 2], p2: stroke.keyPoints[index - 1])
|
||||||
@@ -62,7 +62,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func smoothOutPath(on stroke: Stroke) {
|
private func smoothOutPath(on stroke: Stroke) {
|
||||||
discardVertices(upto: stroke.vertexIndex, on: stroke)
|
discardVertices(upto: stroke.vertexIndex, quadIndex: stroke.quadIndex, on: stroke)
|
||||||
adjustPreviousKeyPoint(on: stroke)
|
adjustPreviousKeyPoint(on: stroke)
|
||||||
switch stroke.keyPoints.count {
|
switch stroke.keyPoints.count {
|
||||||
case 4:
|
case 4:
|
||||||
@@ -79,6 +79,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
let end = CGPoint.middle(p1: stroke.keyPoints[index - 1], p2: stroke.keyPoints[index])
|
let end = CGPoint.middle(p1: stroke.keyPoints[index - 1], p2: stroke.keyPoints[index])
|
||||||
addCurve(from: start, to: end, by: control, on: stroke)
|
addCurve(from: start, to: end, by: control, on: stroke)
|
||||||
}
|
}
|
||||||
|
stroke.quadIndex = stroke.quads.count - 1
|
||||||
stroke.vertexIndex = stroke.vertices.endIndex - 1
|
stroke.vertexIndex = stroke.vertices.endIndex - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +106,8 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
case .random:
|
case .random:
|
||||||
rotation = CGFloat.random(in: 0...360) * .pi / 180
|
rotation = CGFloat.random(in: 0...360) * .pi / 180
|
||||||
}
|
}
|
||||||
let quad = Quad(origin: point, size: stroke.thickness, color: stroke.color, rotation: rotation)
|
let quad = stroke.addQuad(at: point, rotation: rotation, shape: .rounded)
|
||||||
stroke._quads.append(quad)
|
stroke.vertices.append(contentsOf: quad.generateVertices(stroke.color))
|
||||||
stroke.vertices.append(contentsOf: quad.generateVertices())
|
|
||||||
stroke.vertexCount = stroke.vertices.endIndex
|
stroke.vertexCount = stroke.vertices.endIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
let factor: CGFloat
|
let factor: CGFloat
|
||||||
switch configuration.granularity {
|
switch configuration.granularity {
|
||||||
case .automatic:
|
case .automatic:
|
||||||
factor = min(3.5, 1 / (stroke.thickness * 1 / 10))
|
factor = min(5, 1 / (stroke.thickness * 1 / 50))
|
||||||
case .fixed:
|
case .fixed:
|
||||||
factor = 1 / (stroke.thickness * stroke.penStyle.anyPenStyle.stepRate)
|
factor = 1 / (stroke.thickness * stroke.penStyle.anyPenStyle.stepRate)
|
||||||
case .none:
|
case .none:
|
||||||
@@ -145,15 +145,28 @@ struct SolidPointStrokeGenerator: StrokeGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func discardVertices(upto index: Int, on stroke: Stroke) {
|
private func discardVertices(upto index: Int, quadIndex: Int, on stroke: Stroke) {
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
stroke.vertices.removeAll()
|
stroke.vertices.removeAll()
|
||||||
stroke._quads.removeAll()
|
discardQuads(from: quadIndex + 1, on: stroke)
|
||||||
} else {
|
} else {
|
||||||
let count = stroke.vertices.endIndex
|
let count = stroke.vertices.endIndex
|
||||||
let dropCount = count - (max(0, index) + 1)
|
let dropCount = count - (max(0, index) + 1)
|
||||||
stroke.vertices.removeLast(dropCount)
|
stroke.vertices.removeLast(dropCount)
|
||||||
stroke._quads.removeLast(dropCount / 6)
|
discardQuads(from: quadIndex + 1, on: stroke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func discardQuads(from start: Int, on stroke: Stroke) {
|
||||||
|
let quads = stroke.quads.array
|
||||||
|
Persistence.performe { context in
|
||||||
|
for index in start..<quads.count {
|
||||||
|
if let quad = quads[index] as? Quad {
|
||||||
|
quad.stroke = nil
|
||||||
|
context.delete(quad)
|
||||||
|
stroke.quads.remove(quad)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ final class Stroke: NSManagedObject {
|
|||||||
@NSManaged var style: Int16
|
@NSManaged var style: Int16
|
||||||
@NSManaged var createdAt: Date
|
@NSManaged var createdAt: Date
|
||||||
@NSManaged var thickness: CGFloat
|
@NSManaged var thickness: CGFloat
|
||||||
@NSManaged var strokeQuads: Array<StrokeQuad>
|
@NSManaged var quads: NSMutableOrderedSet
|
||||||
@NSManaged var graphicContext: GraphicContext?
|
@NSManaged var graphicContext: GraphicContext?
|
||||||
|
|
||||||
var angle: CGFloat = 0
|
var angle: CGFloat = 0
|
||||||
@@ -31,7 +31,6 @@ final class Stroke: NSManagedObject {
|
|||||||
var thicknessFactor: CGFloat = 0.7
|
var thicknessFactor: CGFloat = 0.7
|
||||||
|
|
||||||
var vertices: [QuadVertex] = []
|
var vertices: [QuadVertex] = []
|
||||||
var _quads: [Quad] = []
|
|
||||||
var vertexBuffer: MTLBuffer?
|
var vertexBuffer: MTLBuffer?
|
||||||
var vertexCount: Int = 0
|
var vertexCount: Int = 0
|
||||||
|
|
||||||
@@ -56,18 +55,25 @@ final class Stroke: NSManagedObject {
|
|||||||
func finish(at point: CGPoint) {
|
func finish(at point: CGPoint) {
|
||||||
penStyle.anyPenStyle.generator.finish(at: point, on: self)
|
penStyle.anyPenStyle.generator.finish(at: point, on: self)
|
||||||
keyPoints.removeAll()
|
keyPoints.removeAll()
|
||||||
NSLog("[Memola] - \(_quads.count) quads")
|
NSLog("[Memola] - \(quads.count) quads")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadVertices() {
|
func loadVertices() {
|
||||||
vertices = strokeQuads
|
vertices = quads
|
||||||
.flatMap { $0.quad.generateVertices() }
|
.flatMap { ($0 as? Quad)?.generateVertices(color) ?? [] }
|
||||||
vertexCount = vertices.endIndex
|
vertexCount = vertices.endIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveQuads() {
|
func addQuad(at point: CGPoint, rotation: CGFloat, shape: QuadShape) -> Quad {
|
||||||
strokeQuads = _quads.map(StrokeQuad.init)
|
let quad = Quad(context: Persistence.context)
|
||||||
_quads.removeAll()
|
quad.id = UUID()
|
||||||
|
quad.origin = point
|
||||||
|
quad.rotation = rotation
|
||||||
|
quad.size = thickness
|
||||||
|
quad.shape = shape.rawValue
|
||||||
|
quads.add(quad)
|
||||||
|
quad.stroke = self
|
||||||
|
return quad
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +86,9 @@ extension Stroke: Drawable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
func draw(device: MTLDevice, renderEncoder: MTLRenderCommandEncoder) {
|
||||||
|
if isEmpty {
|
||||||
|
loadVertices()
|
||||||
|
}
|
||||||
guard !isEmpty else { return }
|
guard !isEmpty else { return }
|
||||||
prepare(device: device)
|
prepare(device: device)
|
||||||
renderEncoder.setFragmentTexture(texture, index: 0)
|
renderEncoder.setFragmentTexture(texture, index: 0)
|
||||||
|
|||||||
@@ -49,6 +49,15 @@ class History: ObservableObject {
|
|||||||
|
|
||||||
func resetRedo() {
|
func resetRedo() {
|
||||||
redoCache = redoStack
|
redoCache = redoStack
|
||||||
|
for event in redoStack {
|
||||||
|
switch event {
|
||||||
|
case .stroke(let stroke):
|
||||||
|
Persistence.performe { context in
|
||||||
|
context.delete(stroke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Persistence.saveIfNeeded()
|
||||||
redoStack.removeAll()
|
redoStack.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,10 @@ class CanvasViewController: UIViewController {
|
|||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
Persistence.shared.viewContext.refresh(canvas, mergeChanges: false)
|
history.resetRedo()
|
||||||
|
Persistence.performe { context in
|
||||||
|
context.refresh(canvas, mergeChanges: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ class Persistence {
|
|||||||
|
|
||||||
static let shared: Persistence = Persistence()
|
static let shared: Persistence = Persistence()
|
||||||
|
|
||||||
private init() {
|
private init() { }
|
||||||
QuadValueTransformer.register()
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy var viewContext: NSManagedObjectContext = {
|
static var context: NSManagedObjectContext = {
|
||||||
|
shared.persistentContainer.viewContext
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var viewContext: NSManagedObjectContext = {
|
||||||
persistentContainer.viewContext
|
persistentContainer.viewContext
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -65,4 +67,18 @@ class Persistence {
|
|||||||
fatalError("[Memola]: \(error.localizedDescription)")
|
fatalError("[Memola]: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static func performe(_ action: (NSManagedObjectContext) -> Void) {
|
||||||
|
action(shared.viewContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func saveIfNeeded() {
|
||||||
|
if context.hasChanges {
|
||||||
|
do {
|
||||||
|
try context.save()
|
||||||
|
} catch {
|
||||||
|
NSLog("[Memola] - \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
//
|
|
||||||
// QuadValueTransformer.swift
|
|
||||||
// Memola
|
|
||||||
//
|
|
||||||
// Created by Dscyre Scotti on 5/8/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import CoreData
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
@objc(QuadValueTransformer)
|
|
||||||
class QuadValueTransformer: ValueTransformer {
|
|
||||||
static let name = NSValueTransformerName(rawValue: String(describing: QuadValueTransformer.self))
|
|
||||||
|
|
||||||
override class func transformedValueClass() -> AnyClass {
|
|
||||||
StrokeQuad.self
|
|
||||||
}
|
|
||||||
|
|
||||||
override func transformedValue(_ value: Any?) -> Any? {
|
|
||||||
guard let quads = value as? [StrokeQuad] else {
|
|
||||||
assertionFailure("[Memola] - Failed to transform `[Quad]` to `Data`")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let data = try JSONEncoder().encode(quads.map(\.quad))
|
|
||||||
return data
|
|
||||||
} catch {
|
|
||||||
print(error.localizedDescription)
|
|
||||||
assertionFailure("[Memola] - Failed to transform `Quad` to `Data`")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func reverseTransformedValue(_ value: Any?) -> Any? {
|
|
||||||
guard let data = value as? Data else {
|
|
||||||
assertionFailure("[Memola] - Failed to transform `Data` to `Quad`")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let quads = try JSONDecoder().decode([Quad].self, from: data)
|
|
||||||
return quads.map(StrokeQuad.init)
|
|
||||||
} catch {
|
|
||||||
print(error.localizedDescription)
|
|
||||||
assertionFailure("[Memola] - Failed to transform `Data` to `Quad`")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static func register() {
|
|
||||||
let transformer = QuadValueTransformer()
|
|
||||||
ValueTransformer.setValueTransformer(transformer, forName: name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,13 +19,22 @@
|
|||||||
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<relationship name="canvas" maxCount="1" deletionRule="Cascade" destinationEntity="Canvas" inverseName="memo" inverseEntity="Canvas"/>
|
<relationship name="canvas" maxCount="1" deletionRule="Cascade" destinationEntity="Canvas" inverseName="memo" inverseEntity="Canvas"/>
|
||||||
</entity>
|
</entity>
|
||||||
|
<entity name="Quad" representedClassName="Quad" syncable="YES">
|
||||||
|
<attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="originX" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="originY" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="rotation" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="shape" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
|
<attribute name="size" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
|
<relationship name="stroke" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Stroke" inverseName="quads" inverseEntity="Stroke"/>
|
||||||
|
</entity>
|
||||||
<entity name="Stroke" representedClassName="Stroke" syncable="YES">
|
<entity name="Stroke" representedClassName="Stroke" syncable="YES">
|
||||||
<attribute name="color" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformer" customClassName="[CGFloat]"/>
|
<attribute name="color" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformer" customClassName="[CGFloat]"/>
|
||||||
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
|
<attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
<attribute name="strokeQuads" attributeType="Transformable" valueTransformerName="QuadValueTransformer" customClassName="[StrokeQuad]"/>
|
|
||||||
<attribute name="style" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="style" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="thickness" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
<attribute name="thickness" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||||
<relationship name="graphicContext" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="GraphicContext" inverseName="strokes" inverseEntity="GraphicContext"/>
|
<relationship name="graphicContext" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="GraphicContext" inverseName="strokes" inverseEntity="GraphicContext"/>
|
||||||
|
<relationship name="quads" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="Quad" inverseName="stroke" inverseEntity="Quad"/>
|
||||||
</entity>
|
</entity>
|
||||||
</model>
|
</model>
|
||||||
Reference in New Issue
Block a user